2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UTF-8 全角カタカナを Shift_JIS 半角カタカナに変換した上で CSV を出力する

Posted at

お題

  • アプリケーションは文字列を 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 正規化(分解)を適用する
  • 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
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?