JAPAN/MARCデータって何ですか?

最近、図書を管理するシステムを嫌々作らされているのですが💦、図書関連の用語がもうムズカシイ。
知らない単語のオンパレードで、知らないワードが出てくると、ググるか、WIKIペディアで調べてます。
んー、めんどくさい。

で、作っている途中で、JAPAN/MARCデータ、というものがあるらしい、そのファイルを取り込んで本のデータとしてデータベースに格納する、という一見簡単なタスクだなぁ、と高を括ってたんですが、この MARCデータの形式(MARC形式というらしい)の仕様書をダウンロードして、サラッと流し読み・・・そっとファイルを閉じました・・・。

このMARCデータから本の情報を切り出すプログラム、誰か書いてないかなー、とググるもキーワードが悪いのかコレ!っていうものが見つからず・・・。
そうだよな、わざわざ JAPAN/MARCなんて使わなくても Google BooksやopenBDで書誌データをAPIで取れるようにしてれくれてるからなぁ。。。

そうはいっても・・・途中で放り出すわけにもいかないので・・・必要なデータを取ることだけに集中して、データを読み出す処理をJavaScriptで起こすことにした。画面はとりあえずhtmlで組んでプロトタイプを作って、あとでC#とかにポーティングすればいいや。
ってわけで、通常業務の空いた時間にシコシコシコ書いてみました。

変換したデータは、indexedDB APIでブラウザに保存するようにして、その際、Dexie.js というライブラリを使う。
Dexie.js
https://github.com/dexie/Dexie.jshttps://dexie.org/

デモ実装した画面はこちら。(クライアントブラウザのみで動作)

肝心のMARC形式のファイルを読み込むスクリプト本体は・・・今見ても何でこんな実装にしたんだ?っていうほどヒドく、初心者丸出しでお恥ずかしい。
実際に業務で使う予定のものは、もうちょっと改良したバージョンですが・・・それのプロトタイプとなった殴り書きバージョン。

正直なんでMARC形式ってこんなメンドクサイ形式なんだーーーー、と書きながら思いました。。。偉い人がつくるフォーマットは、凡人には理解できん。。。

どこかに、完全な実装例ないのかなぁ。。。教えて!エロい人!

ブラウザでCSV(CP932)⇔XLSXの相互変換

極力サーバーサイドでExcelファイルを処理させたくなくて、(なんちゃって)フロントエンド開発をワチャワチャやっております。

データベースからデータを抜き出すのも、Excelファイルだし、データベースにインポートするデータもExcelファイルです。 もうエクセルを見るのも嫌になってます。

サーバーサイドのプログラムを組む時、データをJSONもしくはCSV形式で扱うと、書くのがラクになります。そこで、ブラウザ上で、CSVとXLSXを相互変換できれば便利だな、と思ったのがきっかけ。

例によって、node.jsモジュールを browserify でワンパッケージにして利用します。(バンドラーって呼ぶみたいです。よく分からん)今回は npmコマンドで exceljs・encoding.js・file-saver の三つのモジュールを取得して、browserify で一つのファイルにパックします。

exceljs は、Excel(xlsx)ファイルを扱うモジュール。ExcelファイルをJavaScriptで扱うライブラリは SheetJS が有名で xlsxだけじゃなくて旧型式のxlsファイルも読むことができて非常に便利なのですが、Pro版じゃないと制限が多いので、最近では ExcelJSの方を使っています。

encoding.js は JavaScript でUTF-8,ShiftJIS(CP932),UTF16などを相互に変換できるライブラリです。ShiftJISのCSVファイルを扱う時に使用します。

file-saverは・・・説明要らないですよね💦 ダウンロードできるようにするヤツです。


動作デモはこちら


手順はいつもどおり。
まずは、package.json 。

今回は 3つのモジュールを一つのファイル(bundle.min.js)にまとめて、実際に使用するときは、そのファイル内(bundle.min.js)に定義されているrequire関数でロードするようにします。

上記package.jsonを適当なディレクトリにおいて下記を実行して、bundle.min.js を生成しておきます。

>> npm install
>> npm run bundle

この bundle.min.js を下記のサンプルのHTMLファイルのように、scriptタグでロードしておきます。

そして・・・肝心の 変換コードは下記のようになります。

特筆すべき点はありませんが・・・encoding-japaneseモジュールで、ShiftJISに変換した後、ShiftJISに変換したものを保存するとき、単純 string型でBlobのコンストラクタに渡すと、再変換されて文字化けを起こしてしまいハマってしまいました。
イマイチあんまり分かってません。

CSVの行区切り、本来は・・・CRLF(0x0D0A)・・・ですよね・・・。workbookからCSVにするとき、worksheetを行毎にループして変換したあと、自前で join(‘\r\n’)とかすればいいんでしょうけど・・・。
workbook.csv.writeBuffer()する際、formatterOptionでrowDelimiterで改行コードを指定すればいいみたい。

フロントエンド、メンドクサイし、ムズカシイし、あんまりやりたくない。。。


動作デモはこちら


XLSX-Renderer and Comlink

これの続きです。
備忘録です。


JSONデータをPHPで作成し(PHPじゃなくてもいいんだけど。。。)、クライアント・ブラウザ側のJavaScriptでエクセルファイルを作成するまではできました。
ただ、問題なのは、取得するJSONデータが結構大きいサイズ(例えば10万件とか)になると、それはそれで厄介です。

なんせ、JSONデータをfetchするだけでそれだけで時間がかかりますし、そのデータをエクセルファイルに差し込んでエクセルファイルを作成する時にも結構な時間がかかります。
その間、「データを取得中・・・エクセルファイルを生成中・・・」とかでUIを止めてもいいのですが・・・それはイマドキのトレンドではありませんよね。

せっかくブラウザがワーカースレッドを使えるようにしてくれているので、別スレッドにそういう処理は任してしまいましょう。
そう! 我々にはグーグル様が書いてくれたComlink がある!(笑)

今回は、サンプルとして↓のように、郵便番号のデータベース(SQLite)から、リクエストに応じてJSONデータを返すPHPで書いたウェブAPIを使用し、そのデータをエクセルファイルに変換するまでを書いてみる。


まずは、ウェブAPIですが・・・これはサクッと書いてしまいましょう。
パラメータ”q”に郵便番号の先頭数文字を指定して前方一致で検索してJSONとしてレスポンスを返します。
郵便番号辞書の元になるデータは日本郵便サイトからCSVファイルが公開されているのでそれを使用しSQLiteに変換して使用しました。

次に、エクセルファイルを作成する部分のJavaScriptコードです。
以前にも書きましたが、この部分はnode.jsとnpm, babel.jsとかが必要です。この部分が全く分からなければこの先見てもワケワカンネー状態になるので、とっととブラウザを閉じてこのサイトの事は忘れてくださいm(__)m

作業ディレクトリを一個作って、以下のpackage.jsonとrenderer.jsを作成。

>> tree .
.
├── package.json
└── renderer.js

※ 2022/02/06 rendererをDedicatedWorkerGlobalScopeに追加するようにrenderer.js を若干修正しました。

で、下記を実行して renderer.pack.min.js が生成されるので、このファイルを workerスレッド内からimportScripts関数でロードします。

>> npm i
>> npm run bundle

これで準備完了です。
ここからは、実際に画面(HTML)とメインスレッドのjs、ワーカースレッドのjsを書いていきます。
例によって Web Workerを利用する際には、Comlink を使ってラクします。

HTML等はデモ画面を参照。

まずは、取得したJSONデータをExcelに差し込むための、テンプレートとなるエクセルファイルを作成します。
フツーにエクセルで作成していきます。Microsoft Officeを持っていない場合は、LibreとかオープンソースのOfficeアプリとかでも構いません。
.xlsx形式のファイルを出力できる表計算アプリであれば何でもいいと思います。

こんな感じです。テンプレート XLSXファイル

これらの書式等は、XLSX-Rendererのドキュメント参照。

次にメインスレッド側のJavaScriptコードです。
特筆すべき点はありません。見ればだいたい分かるでせう。
ワーカースレッドのやり取りに Comlink を使ってます。

このファイルをロードする際は、type属性を text/javascript 等ではなく、module にしないとなんかエラーになるみたいです。
そしてボタンのクリックハンドラを設定します。(ここだけjQueryを使用しています)

最後はワーカースレッド側のJavaScriptコードです。
Comlinkを使って、メインスレッド側に関数を公開しています。

※ 2022/02/06 若干修正

このファイル内で、上記で準備した renderer.pack.min.js をロードします。


上記デモページ


たぶんこの程度の処理なら、わざわざワーカースレッドを起こさなくても、いいかとも思います。実際問題、複雑になるだけですし。
メインスレッドはUI専用にして、雑多な処理を全部 workerスレッドに投げるようにすれば、ちょっとハッピーになれる・・・(かもしれない)。

SharedWorkerとかも興味があるんだけど、スマートフォンであまり対応していないんだよね。やっぱバッテリーの電気を食うからなんだろうか・・・。

Web WorkerとComlinkとスレッドと非同期と

ブラウザで画像ファイルのリサイズ処理の続きです。


※勘違い&間違ってるところがあろうかと思います。注意喚起

Webブラウザではよく非同期処理を行いますよね。
ほらよくJavaScriptってシングルスレッドだよ、っていうじゃないですか。
つい最近まで非同期処理はスレッドを起こしているんだ!って勘違いしてまして・・・お恥ずかしゅうございます。

ブラウザのJavaScriptでもWeb Workerを使って、手軽にマルチスレッドできるじゃないですか?スレッドを使うのはC#と同じで超メンドクサイっていうイメージがあるので、敢えて避けていたんですが・・・流石にそうもいってられなくて手を付けてみよう!と思ったんですぅ。

ま、でもいきなり実戦投入するのは流石に怖いので・・・ ちょっと前にこのブログにも書いた「ブラウザで画像ファイルのリサイズ処理」のWeb Worker版を書くことにしました。まぁ、勉強のつもり。

以前の記事の、resize.image.js を書き換えて、画像の読み込み、リサイズ処理を Web Workerを使って別スレッドに処理を投げよう、とこういうわけです。

注意しないといけないのは、Web WorkerからDOMに関係するオブジェクトへのアクセスは一切できないところ。メインスレッドでのグローバルスコープとしてwindowが登録?されていますが、worker内ではそもそもwindowオブジェクトがありませんし。そのかわりwindow.consoleとかwindow.setTimeoutとかのいくつかの関数は worker内のグローバルスコープに登録されています。

ですが、実験機能として、Canvasオブジェクトに transferControlToOffscreenというメソッドChrome/Edgeのみに実装されてまして、このオフスクリーンキャンバスはWeb Worker内でアレヤコレヤできるみたいです。(OffscreenCanvasはメモリ内のみに存在できる表示できないCanvasみたいな感じ?)
特定ブラウザに依存してしまうのは不本意ですが、いずれほかのブラウザFireFox,Safari等へ実装されることを期待しましょう😅

Web Worker自体は window.postMessageメソッド/onMessageイベントと同じような仕組みでデータをやりとりします。が、こういうめんどくさい処理を代行してメインスレッドから透過的に非同期処理と同じような感覚でWeb Workerを扱えるようにしてくれる、Comlink というライブラリがGoogleの開発者によって公開されています。たぶん、Web Worker を使う場合は実質的にこのComlinkというライブラリを使っていくのがスタンダードなやり方になるんだろうと思います。


Comlink
Comlink makes WebWorkers enjoyable. Comlink is a tiny library (1.1kB), that removes the mental barrier of thinking about postMessage and hides the fact that you are working with workers.


Comlinkは・・・worker側でオブジェクトやクラスや関数をメインスレッド側に公開 Comlink.expose(x) し、メインスレッド側で new Workerされた Workerオブジェクトを Comlink.wrap(w) することで、worker側で公開されたオブジェクトやクラスや関数をメインスレッド側から利用できるようにするライブラリで、postMessageメソッド/onMessageイベントなどを意識しないコーディングができるので非常に便利です。
通常のWorkerのほかにもSharedWorker/ServiceWorkerにも対応しています。使い方も上記 githubにあるサンプルコードを読めば書き方は分かると思います。

githubからzipファイルをダウンロードするのもいいのですが、nodejs/npmでインストールするのが簡単なので・・・

>> npm i comlink

comlink/dist/{esm,umd} 以下に ロードするファイルがありますのでこれらを import文で読み込む場合は dist/esm以下のディレクトリ、また、Web Workerに処理させる worker.js内では dist/umd 以下のファイルをimportScripts関数でロードするようです。この辺、ちゃんと勉強していないので、正直よくわからん。

【↓の説明のデモ】 chrome/edgeのみ
※説明では upload.php で送信するようになってますが、このデモでは選択したローカルの画像は、一切送信されないように若干修正しています。

まずは、resize.image.js を以下のように修正します。
このファイルの大部分は worker.js に移動させ、このresize.image.js自体は Web Workerに処理を投げるためだけのプロキシー的な役割に変更しました。要は、DOM要素のCanvasオブジェクトを作って、このオブジェクトからOffScreenCanvasを取得し、[input type=”file”] から得られる Fileオブジェクトともに画像リサイズ処理を担う worker.js に渡します。
この際、Fileオブジェクトはそのまま渡せるのですが、OffscreenCanvasはComlink.transferメソッドでラップして渡します。そのまま渡すとエラーになるみたい。

Web Worker側に値を渡す場合、すべてディープコピーされて渡されます。https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage や、https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm を参照。

この他にも Transferableインターフェイスを実装されているオブジェクトも渡せるようです。このあたりは、MDNサイトのドキュメントを参照

Comlinkは、こういった postMessage/onMessageイベントでのやりとりを、通常のメソッドをコールするような感じでworker側に処理を行わせる事ができるようにしてくれていますので、手順さえ踏めば、非常に簡単に Web Workerを扱えるようになります。

そして、肝心の worker.js です。
前に書いたリサイズの処理は web worker では使えません。以前は new ImageとしてDOMのImageオブジェクトを作成して Canvas APIでゴニョゴニョしてましたが、Web Worker ではそもそも DOMのImageオブジェクトが使えません。
どうするかというと、createImageBitmap関数を使います。これは DOM要素のwindowオブジェクトにもありますし、Web Workerでの グローバルスコープにも登録されているので、この関数に Fileオブジェクトをそのまま放り込んでOffscreenCanvasで使用できる画像を生成します。

これらを以前と同じようにスクリプトから上記のresize.image.js をimportして使用します。

【デモページ】 chrome/edgeのみ
※このデモでは選択したローカルの画像は、一切送信されないように修正しています。

F12キーで開発ツールを表示させ、デバッグすると、確かに スレッドが作られています。

画像一つのリサイズ処理であればあまりメリットを感じませんが、複数の画像処理が走りつつ、メインスレッドでUI処理が走っている場合にメリットを感じるケースもあるかと思います。以前書いた、JSONデータをダウンロードしてエクセル出力する、という一連のフローを Web Workerでスレッド化することも可能かも、まだ試していませんが・・・。

ウェブ上のゲームとか、重い計算処理をメインスレッド以外のスレッドでやればメリットは大きいのではないかと思います。エントリPCであっても CPUコアが4つ以上詰んでるのはザラにありますしね・・・。

今更ながら Visual Studio Code でデバッグ環境構築

2年ぐらいかかっていた業務用のウェブシステムのリプレース案件がこの夏ようやく完了。
なんせ仕様調査からシステム構築、デザイン、データベース設計、コーディングをたった一人でやって(web系が分かる人が僕しかいいない環境なので・・・)、前のシステムにあった訳の分からない仕様を撲滅しつつ、妥協できるところは妥協し、譲れないところは譲らず我を通したせいで文句を言われつつ、自分にとってなんとか納得のいくものができたつもり。
幸いなことに、使っているユーザーさんから、前より使いやすくなった、との声をもらえたのでそれだけでもよかったかな、と思います。
あとは営業さんがユーザーさんに簡単な講習会をやって終わり・・・という段階になってやっと強烈なストレスから解放されました。

前置きはともかく・・・

ちょっと時間が空いてきたので、前から興味があった Visual Studio Codeを触ってみよう、と思いいろいろ調べてある程度のデバッグ環境を構築できてきました。そもそもVisual Studio Codeを使ってみようと思ったきっかけは・・・

  • C#やC++で一つのファイルで完結してしまうような小さいコンソールプログラムをたまに書くけど・・・簡単なデバッグ環境が欲しい
  • msbuildとか要らん!そもそも XML は好かん。 nmakeで必要十分だし書くの慣れてる。
  • Visual Studio 2019のIDEを使えば手っ取り早いんだけど・・・おいらのPC環境のせいかどうか分からないが、エディタのフォント表示が異常に汚い(ガタガタ)
  • Visual Studio 2019のIDEを使うほど大したコードを書くわけじゃないし、何より動作が重いし、キーバインドが慣れない
  • 拡張機能でvimのキーバインドがほぼ完璧に再現できるみたい?

早速インストールし、必要そうな拡張機能を入れて使いだしたんだけど、問題が発生。
ターミナル>ビルドタスクの実行を行うと・・・cl.exe、csc.exe、link.exe などのツールへのPATH設定や環境変数などが定義されていないのでエラーに!

普段、c++やc#のコードをビルドするとき、Visual Studio 2019をインストールした時に入る、vcvars64.bat というBATファイルをコンソールウィンドウから実行することで Visual Studio 2019のビルドツールを使えるようにしてたんですが・・・はて・・・ターミナルの設定はどうすればいいのか・・・考えたあげく、下記のような コマンドファイルを作ってこれを setting.json の Terminal.Integrated.Profiles というキーに入れた。

REM ------------------
REM vs2019t.cmd
REM ------------------
@ECHO OFF
CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
%comspec% %*

visual studio 2019のコンソール環境用にterminalのprofileを一つ追加。

下記のように デフォルトのプロファイルとして設定した。

ホントは知らないだけで、もっとスマートな方法があるのかもしれん!
結構ググったんだけど見つけられなかった。。。

で、実際のデバッグ環境としてはかなり良くなった!

ターミナル>既定のビルドタスクの構成からtasks.jsonを作成してビルド方法を書くみたいだけど、一つのファイルをデバッグしたい場合は、cl.exe/csc.exeとかのコマンドを設定して、
複数のファイルがある場合は、nmakeかmsbuildとかにした方が効率がいいかもしれない。
tasks.jsonのテンプレートをいくつか用意しておいた方がいいかもしれん。

vscode/tasks.json テンプレ

.vscode/launch.json テンプレ