このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

令和の時代に、JavaScriptで Shift-JISファイル作成 全銀データフォーマットに対応する。

JavaScript

2020.03.03

どもです。

いやぁ。

世間は新型コロナウィルスで大変ですよね。

当方も、先週頭からリモートワークに切り替わり、正に今から感染が広がっていくのだろうなぁ。と感じております。

後手後手で、グダグダで無茶苦茶な対応で、国内外でかなりの政権批判が発生している状況ですが、

まぁ仕方ないですよね。現政権だと。。

何かを期待する方がおかしいぐらいになっていますもんね。

おかしいなぁ。と思うことは大事ですよね。

それから色々と調べて行くと色々と見えることありますよね。

という事で、時事に関してはこのくらいで今回は、久々に文字コードと戦った事もあってその備忘録でもと。

いいですよね。

この令和の時代に、 Shift-JISを扱うなんて。

時代を遡った感じあって。

でも、文字コードと戦うのがエンジニアぽくて良いですよね。

 

Shift-JISファイル作成・ダウンロード

今回、CSVファイルのダウンロードと、全銀データフォーマット形式のファイル

JavaScriptでファイル生成、ダウンロードの方法を記載しているのですが、

ファイル生成、ダウンロードの方法を先にコードを知りたい方はこちらとなります。

 

ファイルダウンロードを行う関数

export const downloadAsFile = (data) => {
    const BLOB = new Blob([data.content], { type: data.type || 'text/plain' })
    const CAN_USE_SAVE_BLOB = window.navigator.msSaveBlob !== undefined

    if (CAN_USE_SAVE_BLOB) {
        window.navigator.msSaveBlob(BLOB, data.fileName)
        return
    }
    const TEMP_ANCHOR = document.createElement('a')
    TEMP_ANCHOR.href = URL.createObjectURL(BLOB)
    TEMP_ANCHOR.setAttribute('download', data.fileName)
    TEMP_ANCHOR.dispatchEvent(new MouseEvent('click'))
}

UTF-8文字列をShift-JIS文字列に変換する関数

import Encoding from 'encoding-japanese'

toShiftJIS(utf8String) {
    const detected = Encoding.detect(utf8String)
    const unicodeList = []

    for (let i = 0; i < utf8String.length; i += 1) {
        unicodeList.push(utf8String.charCodeAt(i))
    }

    const sjisArray = Encoding.convert(unicodeList, {
        to: 'SJIS',
        from: detected
    })
    return new Uint8Array(sjisArray)
}

と、言った感じで、主要な関数を2つ先に挙げました。

 

CSVファイル  ダウンロード

Google スプレットシート用に、CSVファイルを作成しダウンロード出来るようにする必要があったのですが、

データ自体はAPIでJSONで受け取り、それをCSV形式に変換。

そして、ファイル生成しダウンロード。

と言った流れになります。

CSV形式にするのは、カンマ区切りにすれば良いだけなので、以下の様にカンマ区切りに文字列にすれば取り敢えずはオッケーですね。

getCSVdata関数

getCSVdata() {
    return `${this.id},${this.ampseq},${this.transfee},${this.ct},${this.ut}...`
}

上記でも挙げさせて頂いた様に、ファイルダウンロードを行う関数を見ていきましょう。

ファイルダウンロードを行う関数

export const downloadAsFile = (data) => {
    const BLOB = new Blob([data.content], { type: data.type || 'text/plain' })
    const CAN_USE_SAVE_BLOB = window.navigator.msSaveBlob !== undefined

    if (CAN_USE_SAVE_BLOB) {
        window.navigator.msSaveBlob(BLOB, data.fileName)
        return
    }
    const TEMP_ANCHOR = document.createElement('a')
    TEMP_ANCHOR.href = URL.createObjectURL(BLOB)
    TEMP_ANCHOR.setAttribute('download', data.fileName)
    TEMP_ANCHOR.dispatchEvent(new MouseEvent('click'))
}

まず、引数のdataは「fileName」「type」「content」を持ったオブジェクトとなります。

fileName: ダウンロードする際のファイル名

type: MIME タイプ

content: データ中身

と言った感じで受け取ります。

引数から「content」と「type」を受け取って、Blob関数を用いてBlobオブジェクトを生成。

その後、navigator msSaveBlob APIの存在の有無を確認します。

msSaveBlobはIEに用意されているAPIなので、IEの場合をこれらを用いてファイルダウンロードを実行。

それ意外は、URL.createObjectURLでURLを生成し、a要素を生成し擬似的にクリックしてダウンロードさせるといった処理になります。

 

回してダウンロード

let textData = `${headerString}\n`
targetList.forEach(target => {
    textData += `${target.getCSVdata()}\n`
})
downloadAsFile({
    fileName: `${fileName}.csv`,
    type: 'text/csv',
    content: textData
})

CSVヘッダーも付与したい場合を想定しました。

上記のtargetは、getCSVdata関数をメンバに持ったインスタンス想定です。

必要な分だけ回して、CSV形式のstringに変換し、変数textDataに追加し、上記のダウンロード関数を実行しファイルを生成。

と言った流れになります。

特に、文字コード指定なければ簡単にCSVファイルを生成、ダウンロード出来るかと思います。

全銀データフォーマット  ダウンロード

さて、本題はここからです。

今回、全銀データフォーマット形式のファイルをJavaScriptで作成し、ファイルダウンロードまで行おうといった内容になります。

全銀データフォーマットとはなんぞや?

という方はこちらを御覧ください。

全銀データフォーマット

http://www.hyogokenshin.co.jp/wp-content/uploads/format1.pdf

全銀協規定フォーマットとは、全国銀行協会連合会がデータ伝送を行うために定めたフォーマットになります。

簡単に説明すると、銀行振込を行うための振り込みデータ形式。といったところでしょうか。

上記の形式に作成しないとすぐエラーとなってしまいます。

大まかにポイントは、

・ヘッダー、データ、トレーラー、エンドレコードのデータを作成する

・各レコードは120文字(バイト)で作成する

・各レコードデータはバイト数が決まっている

・半角カナ文字を使用

・Shift-JIS

来ましたねぇ。Shift-JIS。

もう会うことないと思っていたのに、令和の時代でこんにちは。ですね。

それでは、早速各レコード生成の関数を見ていきましょう。

各レコード

createHeaderRecord(data): string {
    return `1210${data.consignorCode}${consignorName}${data.transferSpecifiedDate}${data.bankCode}${data.bankName}${data.bankBranchCode}${data.bankBranchName}${data.accountNo}                 `
}

createDataRecord(data): string {
    const bankCode = data.bankCode
    const bankName = data.bankName.padEnd(15, ' ')
    const branchCode = data.branchCode
    const branchName = data.branchName.padEnd(15, ' ')
    const accountTypeNo = data.accountTypeNo
    const accountNo = data.accountNo
    const accountHolder = data.accountHolder.padEnd(30, ' ')
    const transferAmount = data.transferAmount.padStart(10, '0')
    return `2${bankCode}${bankName}${branchCode}${branchName}    ${accountTypeNo}${accountNo}${accountHolder}${transferAmount}1                             `
}

createFooterRecord(remittanceList): string {
    const totalLength = remittanceList.length
    const totalLengthString = String(totalLength).padStart(6, '0')
    const totalPrice = sumBy(remittanceList, 'withdrawAmount')
    const totalPriceString = String(totalPrice).padStart(12, '0')
    const endEmptyString = ''.padEnd(101, ' ')
    return `8${totalLengthString}${totalPriceString}${endEmptyString}`
}

createEndRecord() {
    return '9'.padEnd(120, ' ')
}

まず、ヘッダーレコードを生成する「createHeaderRecord関数」。

こちらは、動的でないプロパティを想定していますので、そのまま設定された値をstringで返却します。

エンドレコードを生成する「createEndRecord関数」も同様ですね。

表示は、9のみで1行120文字のstringを返却。

そのような感じで、「createHeaderRecord関数、createEndRecord関数」は、ちょっと割愛させてもらいます。

 

で、大事なのは、「createDataRecord関数」。

データの数だけ呼び出しを行い、レコードを作成しております。

データレコードは2始まりで、ところどころスペースがありますが、データを埋めるためのスペースで、

emptyStringとして、変数を作成しても良いですね。

銀行データに関するものなどは、ECMA2017のAPI、padStartとpadEndでゼロパディング&スペース埋めを行いデータを作成しております。

トレーラーレコード生成の「createFooterRecord関数」は、各振り込みデータ数や振込額合計となっております。

sumByは、lodash使用しております。

 

といった訳で、こちらを利用してデータを作成。

そして、上記のダウンロード関数でファイルをダウンロード。

これで、行けただろう。と試したところ。

 

エラー1行120バイト 

なんと。。。

何が原因だ?調べてみるところ、改行コードでは?

と行き着いた。

 

文字コードと改行コード

OS 文字コード 改行コード 改行コードの大きさ
Windows シフトJIS CR+LF 2バイト
Mac OS シフトJIS CR 1バイト
Mac OS X EUC LF 1バイト
UNIX EUC LF 1バイト

全銀データフォーマットは、最後2バイト改行。

じゃないと行けないようだ。。

うーん。。確かに。

1バイトで改行を行っていたので、修正。

'\n' → '\r\n'

よし!

これで大丈夫だろう!!

とテストを行ってみると。

またしても、

エラー 1行120バイト

ファ。

生成されたデータを目視で見てみると、うん??

ハイフンがなんか変な気がする。。

そう。

銀行コードには、「zengin-code」のパッケージを使用していて、

github

https://github.com/zengin-code

「半角カタカナ」変換には、「moji」のパッケージを使用しているのですが、

github

https://github.com/niwaringo/moji

(こちら、最後の修正が npm publishされていないところを注意。)

zengin-code」の 「ミツビシユ-エフジエイ」がどうも変な気がする。。

調べたところ、ASCIIのハイフンということがわかる。

と、同時にこんなにハイフンあったのかよーーーと。

文字 UTF-8 Unicode 説明
2D U+002D ASCIIのハイフン
E383BC U+30FC 全角の長音
E28090 U+2010 別のハイフン
E28091 U+2011 改行しないハイフン
E28093 U+2013 ENダッシュ
E28094 U+2014 EMダッシュ
E28095 U+2015 全角のダッシュ
E28892 U+2212 全角のマイナス
EFBDB0 U+FF70 半角カナの長音

 

参考ページ(助かりました)

https://qiita.com/ryounagaoka/items/4cf5191d1a2763667add

 

なので、以下の様に「moji」を使いつつ、拡張して。

これで、問題ないだろう!!

 

追加

getReplaceHankakuKana(zenkakuKana) {
    const hankakuKana = moji(zenkakuKana)
        .convert('ZKtoHK')
        .toString()
    return hankakuKana.replace(/(-|ー)/g, 'ー')
}

よし。

これでOKですよね!?

エラー 1行120バイト

ファ。

な、な、なんだよ。。。

うん?

そもそも文字コード変換していないなぁ。。

という事で、文字コード、ファイル形式調べてみる。

$ file --mime ./202002_zengin.txt
$ text/plain; charset=utf-8

はい。UTF-8ですよね。

 

上記のdownloadAsFile関数で、MIME タイプを以下の様に指定してみるが適応されない。

type: 'text/plain; charset=shift_jis',

<strong>downloadAsFile関数</strong>

downloadAsFile({
    fileName: `${formattedDate(remittanceList[0].remittanceDate, 'YYYYMM')}_zengin.txt`,
    type: 'text/plain; charset=shift_jis',
    content: this.toShiftJIS(textData)
})
 
downloadAsFile(data) {
  const BLOB = new Blob([data.content], { type: data.type || 'text/plain' })

うーん。出来ないのか。。一旦コマンド変換することにしました。

 

iconvコマンド変換

$ iconv -f utf8 -t sjis 202002_zengin.txt > 202002_zengin.txt
$ text/plain; charset=iso-8859-1

と、変換してテストを行うと問題なく通りました。

 

encoding.jsで、Shift-JIS変換

さて、毎度コマンド叩きたくないのでJS(ブラウザ)で、処理は終わらせたい。と。

良さそうなパッケージを探すところ、「encoding.js」パッケージが良さそうなので早速利用してみる。

 

encoding-japanese

https://github.com/polygonplanet/encoding.js

npmインストール

$ npm install encoding-japanese<

変換できるフォーマット形式は以下のバリエーションの様です。

  • 'UTF32' (detect only)
  • 'UTF16'
  • 'UTF16BE'
  • 'UTF16LE'
  • 'BINARY' (detect only)
  • 'ASCII' (detect only)
  • 'JIS'
  • 'UTF8'
  • 'EUCJP'
  • 'SJIS'
  • 'UNICODE' (JavaScript Unicode Array)

UTF-8形式から変換しないとな。とか思っていたのですが、

「Encoding.detect関数」で文字コードを確認出来るのですが、確認するところ「UNICODE(JavaScript Unicode Array)」形式なのかよ。

Encoding.detect(textData)
UNICODE

では、これで変換。

toShiftJIS(utf8Array) {
    const detected = Encoding.detect(utf8Array)
    return Encoding.convert(unicodeList, {
        to: 'SJIS',
        from: detected
    })
}

行けたでしょ。?

と思っていたら、MIME タイプ 文字コードも変換されず、文字化けする始末。

$ text/plain; charset=utf-8

およよ。。

と、ふと考えるとこれは、一文字ずつ変換する必要があるな。。

ということに気がつく。

というわけで、1文字単位で変換することにする。

また、charCodeAt関数でUTF-16 コードユニット形式に変換し、配列に格納。

Encoding.convertで、SJISフォーマットに変換。

ES2017のAPIである、Uint8Arrayで、負の数なし整数(Unsigned int)の配列のバイナリ変換。

toShiftJIS(utf8Array) {
    const detected = Encoding.detect(utf8Array)
    const unicodeList = []
    for (let i = 0; i < utf8Array.length; i += 1) {
        unicodeList.push(utf8Array.charCodeAt(i))
    }
    const sjisArray = Encoding.convert(unicodeList, {
        to: 'SJIS',
        from: detected
    })
    return new Uint8Array(sjisArray)
}

utf8Array.charCodeAt

[12362, 12414, 12360, 12399, 12354, 12411, 12363, 32, 32, 32, 32, 32, 32, 32, 32,...]

new Uint8Array(sjisArray)

[74, 126, 72, 111, 66, 123, 75]

これで、問題ないでしょ。

 

変換完了

$ text/plain; charset=iso-8859-1

お!

MIME タイプも変換されました。

でも、iso-8859-1なのですね。

テストするところ。。

オッケーでしたー。

と、長々書いてきましたが、久々 JSで文字コードと戦ったログでした。

ではではぁ。

Comment

Related Article

OAuthのフローを可視化できるツールを作ってみました。

2020.05.17

令和の時代に、JavaScriptで Shift-JISファイル作成 全銀データフォーマットに対応する。

2020.03.03

インターネットにて世論調査を行う「世論Web」サービスを始めてみました。

2020.01.31

年末のレトロゲーム熱の際、ファミコンソフト一覧パッケージ作ってました。

2020.01.24

あと10日で「jsdo.it」のサービスが終わってしまう!! ソースダウンロードまだの方は急げぇ〜!

2019.10.21

正規表現 先読み後読み 論理積

2019.07.28

「二段階認証?」という方も 5分で覚える パスワードレス WebAuthnのまとめ

2019.07.07

上級者向け JavaScript 問題集 「javascript-questions」日本語翻訳担当してます。

2019.06.22

SPAサイトでの認証認可 JWT✗Rails5✗Nuxt.js

2019.03.24

Nuxt.js と auth-module (@nuxtjs/auth)で、JWT(JSON Web Tokens)& OAuth 認証 ログイン

2019.02.21

CATEGORY LIST

LATEST NEWS

Mac Home brewでSDL2.0を簡単に環境設定

C

2020.06.24

Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その3

Python

2020.06.14

Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その2

Python

2020.06.08

Django django-rest-auth + Nuxt.js auth-module で作る SPA JWT OAuth ログインシステム その1

Python

2020.06.07

このコロナ禍で、飛沫感染防止など求められる中「電子メモパッド」が重宝。 1300円で購入可能な電子メモパッドが超絶便利な件。

tool

2020.06.02

OAuthのフローを可視化できるツールを作ってみました。

JavaScript

2020.05.17

Django django-allauthで、サクッとソーシャルログイン機能を実装

Python

2020.04.12

部下を育てる技術

イベント

2020.04.08

令和の時代に、JavaScriptで Shift-JISファイル作成 全銀データフォーマットに対応する。

JavaScript

2020.03.03

インターネットにて世論調査を行う「世論Web」サービスを始めてみました。

JavaScript

2020.01.31

まだ間に合う!!ラズベリーパイ購入なら「RSコンポーネンツ」で!最大40%オフの年に1度の大特価期末セール中!

RaspberryPi

2020.01.25

年末のレトロゲーム熱の際、ファミコンソフト一覧パッケージ作ってました。

JavaScript

2020.01.24

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US