お題
- アプリケーションは文字列を UTF-8 の全角カタカナなどで管理している
- CSV は Shift_JIS の半角カタカナなどで構成したい
- 具体的には
ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモ ヤユヨラリルレロワヲン ァィゥェォャュョッ ゙(濁点) ゚(半濁点) \ ( ) -(ハイフン) ー(長音) / . , ・ _ 「 」 半角スペース
だけを使わないといけない - ref. https://www.tanshin.co.jp/business/netbk/pdf/zengin_moji.pdf
- 具体的には
やること
-
csv.NewWriter を使って UTF-8 文字列を Shift_JIS に変換する
- ex. efbe80 を c0 にする
- ただこれだけだと、半角にならないので width.Narrow transformer を使って全角を半角にする
- ex. タ を タ にする
- まだこれだけだと、濁点を含むカタカナを Shift_JIS に変換できないので norm.NFD transformer を使って Unicode 正規化(分解)を適用する
- ex. ダ を タ と 濁点にする
- 補足: Shift_JIS の 1 バイトコード内に ダはない
- transformer は transform.Chain でまとめられるので順番に並べる
コード
全銀仕様をベースにした振込申請を CSV で出力する。
package main_test
import (
"encoding/csv"
"io"
"testing"
"bytes"
"strconv"
"log"
"fmt"
"io/ioutil"
"strings"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"golang.org/x/text/width"
)
// NewCSVWriter は w に CSV を書き出す Writer を生成する。
func NewCSVWriter(w io.Writer) *csv.Writer {
return csv.NewWriter(transform.NewWriter(
w,
transform.Chain(
// ex. ダをタと濁点に分解する
norm.NFD,
// ex. タをタに、濁点を ゙ にそれぞれ変換する
width.Narrow,
// ex. efbe80 を c0 に efbe9e を de にそれぞれ変換する
japanese.ShiftJIS.NewEncoder(),
),
))
}
// BankTransferRequest は振込申請を表す。
type BankTransferRequest struct {
// BankCode は銀行コードを表す。
BankCode string
// BranchCode は支店コードを表す。
BranchCode string
// AccountTypeCode は口座種別コードを表す。
AccountTypeCode string
// AccountNumber は口座番号を表す。
AccountNumber string
// RecipientName は受取人名を表す。
RecipientName string
// TransferAmount は振込金額を表す。
TransferAmount int64
}
// CSVHeaderKey はヘッダーの位置を表す。
type CSVHeaderKey int
const (
// BankCode は銀行コードを表す。
BankCode CSVHeaderKey = iota
// BranchCode は支店コードを表す。
BranchCode
// AccountTypeCode は口座種別コードを表す。
AccountTypeCode
// AccountNumber は口座番号を表す。
AccountNumber
// RecipientName は受取人名を表す。
RecipientName
// TransferAmount は振込金額を表す。
TransferAmount
// EndHeaderKey はヘッダー定義の番兵を表す。それ以外の意味はない。
EndHeaderKey
)
// CSVHeader は振込の申請に使う CSV のヘッダーレコードを返す。
func CSVHeader() []string {
var header []string
for i := BankCode; i < EndHeaderKey; i++ {
header = append(header, i.String())
}
return header
}
func (headerKey CSVHeaderKey) String() string {
switch headerKey {
case BankCode:
return "BankCode"
case BranchCode:
return "BranchCode"
case AccountTypeCode:
return "AccountTypeCode"
case AccountNumber:
return "AccountNumber"
case RecipientName:
return "RecipientName"
case TransferAmount:
return "TransferAmount"
default:
log.Printf("[BUG] not found headerKey %d", headerKey)
return "Unknown"
}
}
// ConvertBankTransferRequestsToShiftJISCSV は UTF-8 文字列を含む振込申請のスライスを
// ShiftJIS 文字列を含む振込申請の CSV に変換して返す。
func ConvertBankTransferRequestsToShiftJISCSV(
requests []*BankTransferRequest,
) (
[]byte,
error,
) {
buf := &bytes.Buffer{}
w := NewCSVWriter(buf)
if err := w.Write(CSVHeader()); err != nil {
return nil, err
}
for _, request := range requests {
record := []string{
request.BankCode,
request.BranchCode,
request.AccountTypeCode,
request.AccountNumber,
request.RecipientName,
strconv.FormatInt(request.TransferAmount, 10),
}
if err := w.Write(record); err != nil {
return nil, err
}
}
w.Flush()
return buf.Bytes(), nil
}
func TestCreateShiftJISCSV(t *testing.T) {
tests := []struct {
name string
inputRequests []*BankTransferRequest
wantCSV func() ([]byte, error)
}{
{
name: "濁点がないカタカナだけの振込申請を CSV に変換できる",
inputRequests: []*BankTransferRequest{
{
BankCode: "1",
BranchCode: "123",
AccountTypeCode: "4",
AccountNumber: "1234567",
RecipientName: "ヤマナシ タロウ",
TransferAmount: 1000,
},
},
wantCSV: func() ([]byte, error) {
csv := fmt.Sprintf("%s,%s,%s,%s,%s,%d",
"1",
"123",
"4",
"1234567",
"ヤマナシ タロウ",
1000,
)
return utf8Tosjis(csv)
},
},
{
name: "濁点があるカタカナの振込申請を CSV に変換できる",
inputRequests: []*BankTransferRequest{
{
BankCode: "1",
BranchCode: "123",
AccountTypeCode: "4",
AccountNumber: "1234567",
RecipientName: "ヤマナシ タロウ",
TransferAmount: 1000,
},
{
BankCode: "2",
BranchCode: "223",
AccountTypeCode: "1",
AccountNumber: "2234567",
RecipientName: "ヤマダ タロウ",
TransferAmount: 100000,
},
},
wantCSV: func() ([]byte, error) {
csv := fmt.Sprintf("%s,%s,%s,%s,%s,%d",
"1",
"123",
"4",
"1234567",
"ヤマナシ タロウ",
1000,
)
csv += fmt.Sprintf("\n%s,%s,%s,%s,%s,%d",
"2",
"223",
"1",
"2234567",
"ヤマダ タロウ",
100000,
)
return utf8Tosjis(csv)
},
},
}
for _, test := range tests {
got, err := ConvertBankTransferRequestsToShiftJISCSV(test.inputRequests)
if err != nil {
t.Errorf("[%s] got err %v, but want err nil", test.name, err)
}
wantCSV, err := test.wantCSV()
if err != nil {
t.Errorf("[%s] got err %v", err)
continue
}
if string(got) != string(wantCSV) {
t.Errorf(
"[%s] got %s, but want %s (\n%x\n%x\n)",
test.name,
string(got),
string(wantCSV),
got,
wantCSV,
)
}
}
}
func utf8Tosjis(content string) ([]byte, error) {
csv := "BankCode,BranchCode,AccountTypeCode,AccountNumber,RecipientName,TransferAmount\n"
csv += content
r := transform.NewReader(strings.NewReader(csv), japanese.ShiftJIS.NewEncoder())
bs, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return bs, err
}