このサイトは、只今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

Go言語でAssetsもシングルバイナリ化に便利な packr パッケージ

go

2020.11.25

Maker Faire Tokyo 2020に行ってきました。

イベント

2020.10.06

パックマン 解析プログラム動画から見る 追跡アルゴリズム

AI・Bot・algorithm

2020.09.23

Django (DRF)で、ユーザーのモデルを作成時にハッシュidを別で保存する。

Python

2020.09.10

ゲオのサマーセール 980円以下のゲームソフトが半額!8月16日(日)まで。で購入したもの。

Game

2020.08.13

Webassembly用いて、SDL 2.0をブラウザでレンダリング

C++

2020.08.10

macOS pyenv環境でtkinterが動かないので、再度インストール

Python

2020.08.09

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

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

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

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

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

FOLLOW US