Private Node “Excel → JSON変換ノード” を使いやすくしてみる

 「Private Node でExcel → JSON変換ノードを作ってみる」の記事では、 Private Node 機能を使ってExcelファイルをJSONオブジェクトに変換するオリジナルのノード”Xlsx2Json”を作りました。この記事では、”Xlsx2Json”ノードをさらに使いやすく便利にしてみます。また、 Private Nodeのバージョン管理についても説明します。

目次

はじめに

 enebularのPrivate Node機能では、NPM に公開することなく自作のNode-REDノードを作成して使用することができます。Private NodeでExcel → JSON変換ノードを作ってみる」の記事では、ExcelファイルをJSONオブジェクトに変換するノード”Xlsx2Json”をPrivate Nodeとして作成しました。

 この記事では、“Xlsx2Json”ノードに機能を追加して、バージョンアップする様子を紹介します。

 NPMNode-RED Libraryに公開されたノードを見ていると、人気のノードの多くが頻繁にバージョンアップを繰り返して、機能を追加したりバグを修正したりしていますね。

 自作のPrivate Nodeでも、やはり機能追加やバグ修正は必要になってきます。はじめは最小限の機能でリリースして、後から機能を少しずつ追加していくような開発方法を取る場合は、頻繁なバージョンアップは必須になってきます。

 enebularには、Private Nodeのバージョン管理機能があります。この機能を使うことで、古いバージョンを保存しておいたり、古いバージョンに戻したりすることができます。

Private Node “Xlsx2Json” の課題と解決策

“Xlsx2Json” ノードの使いづらさ

 Private Node でExcel → JSON変換ノードを作ってみる」の記事の “おわりに” では、以下のような課題について記載しました。

ところで、今回作った Xlsx2Json ノードには、まだまだ改良の余地があります。上のフローの図を見ても違和感があるように、どうせなら Http-Requestノードの出力をそのままXlsx2Jsonノードに入力できるようにしたいですよね。

 前回の記事で作ったフローは下図です。

Private Node を使ったフロー

 フローが2つに分割されています。前回の記事で作った "Xlsx2Json" ノードは、Excelファイルをノード内で読み込んでJSONオブジェクトに変換します。そのため、"Http-request" ノードでダウンロードしたExcelファイルは、わざわざ一度 "File" ノードを使ってファイルに書き込まなければいけません。

解決策 “ファイルを直接受け取れるようにする”

 "Http-request" ノードは、msg オブジェクトのpayloadとしてダウンロードしたファイルを出力します。"Xlsx2Json"ノードがこの出力を直接受け取ることができれば、一度ファイルに書き込む手間がなくなります。

 そこで、 "Xlsx2Json" ノードを以下のように修正します。

  1. FilenameにExcelファイルのファイル名(パス)が指定されていた場合は、Excelファイルを読み込んでJSONオブジェクトに変換する
  2. Filenameが空の場合は、入力される msg オブジェクトの msg.payload をExcelファイルとして読み込んで、JSONオブジェクトに変換する

ソースコードの修正

 では、 msg.payloadを読み込む処理を追加します。

 以下が元のファイルです。

const XLSX = require('xlsx');

module.exports = function(RED) {
    function Xlsx2JsonNode(config) {
        RED.nodes.createNode(this,config);
        this.filename = config.filename;
        const node = this;        

        node.on('input', function(msg, send, done) {
            send = send || function() { node.send.apply(node,arguments) };
            if (node.filename) {
                try {
                    const workbook = XLSX.readFile(node.filename);
                    msg.payload = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
                }
                catch(err) {
                    msg.payload = {
                        "error": err,
                        "filename": node.filename, 
                    };
                }
            } else {
                msg.payload = { "error": "no filename" };
            }
            node.send(msg);
            if (done) {
                done();
            }
        });
    }
    RED.nodes.registerType("xlsx2json",Xlsx2JsonNode);
}

 以下のソースコードが、修正後のファイルです。

const XLSX = require('xlsx');

module.exports = function(RED) {
    function Xlsx2JsonNode(config) {
        RED.nodes.createNode(this,config);
        this.filename = config.filename;
        const node = this;        

        node.on('input', function(msg, send, done) {
            send = send || function() { node.send.apply(node,arguments) };
            if (node.filename) {
                try {
                    const workbook = XLSX.readFile(node.filename);
                    msg.payload = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
                }
                catch(err) {
                    msg.payload = {
                        "error": err,
                        "filename": node.filename, 
                    };
                }
            } else {
                try {
                    const workbook = XLSX.read(msg.payload, {type:"buffer"});
                    msg.payload = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
                }
                catch (err) {
                    msg.payload = {
                        "error": err,
                        "filename": "msg.payload", 
                    };
                }
            }
            node.send(msg);
            if (done) {
                done();
            }
        });
    }
    RED.nodes.registerType("xlsx2json",Xlsx2JsonNode);
}

 node.filename に値が設定されていなかった場合(if (node.filename)の処理)、 msg.payload のデータをExcelファイルのバイナリデータとみなして読み込み、JSONオブジェクトに変換します。具体的には以下の処理です。

const workbook = XLSX.read(msg.payload, {type:"buffer"});
msg.payload = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

 Excelファイルの読み込みか、JSONオブジェクトへの変換で例外が発生した場合に備えて、try … catchで包んでいます。

 バージョンアップしたので、package.jsonのバージョンも0.1.00.2.0に変更しておきましょう。

{
  "name": "enebular-privatenode-contrib-xlsx2json",
  "version": "0.2.0",
  "description": "",
  ...

 enebularへの登録に備えて、enebular-privatenode-contrib-xlsx2jsonフォルダで以下のコマンドを入力して、.tgzファイルを作成しておきます。

npm pack

 バージョンが0.2.0に変わっているので、出来上がるファイルもenebular-privatenode-contrib-xlsx2json-0.2.0.tgzになります。

Private Node のバージョン管理

既存バージョンにバージョン名を付ける

 enebularのPrivate Node機能には、バージョンを管理する機能があります。enebularの公式ドキュメントの「プライベートノードのバージョン管理」でも詳しく説明されていますので、読んでみてください。

 せっかくなので、この機能を使って新しいバージョンの Xlsx2Json ノードを管理してみます。enebularにSign-Inしたら、Xlsx2Jsonノードを開いてください。”Version”タブを選択します。

 まず、既存のXlsx2Jsonノードにバージョン名をつけます。package.jsonのバージョンに合わせて、”0_1_0″にしてみましょう(機能の制約でピリオドはバージョン名に使えないので、アンダースコアで代替します)。”Create New Version”ボタンを押すと以下のような画面になりますので、”Title”に”0_1_0″と入力します。”Comment”欄には好きな説明を入力してください。

 入力が終わったら”Create”ボタンを押せば、”0_1_0″と名前のついたバージョンが生成されます。

 これで、いつでも古いバージョンに戻すことができるようになりました。

新しいノードをアップロードする

 それでは、新しくなったノードをアップロードしてみましょう。

 enebularの”Overview”タブを選択します。

 ”Update”ボタンを押すと以下のような画面になりますので、先ほど作成したenebular-privatenode-contrib-xlsx2json-0.2.0.tgzファイルを選択します。

 ファイルをアップロードすると、”Overview”タブに表示される”Package Version”が0.2.0に変わったのが確認できます。

新バージョンにバージョン名をつける

 新しいバージョンにもバージョン名をつけておきましょう。package.jsonのバージョンに合わせて、”0_2_0″にします。

 ”Overview”タブから、”Version”タブに移動します。”Create New Version”ボタンを押してバージョン作成の画面を表示して、”Title”に”0_2_0″、”Comment”には好きな説明(例えば「Excelファイルをmsg.payloadとして受け取る処理を追加」)を入力し、”Create”ボタンを押してください。

 これで、新しいバージョンが作成されます。

“Xlsx2Json” を使ってみる

 いよいよ、バージョンアップした Xlsx2Json ノードを使ってみます。

 下のフロー図を見てください。これは「Private Node でExcel → JSON変換ノードを作ってみる」の記事で作成したフローを改造したものです。フローが一本に繋がっています。Http-requestノードの出力が直接Xlsx2Jsonノードに入力されています。

 前回の記事では、Http-requestノードの出力は、一旦Fileノードでファイルに保存していました。その処理を削除して、Xlsx2Jsonノードの入力に接続されています。

 以下のJSONファイルは、図のフローをエクスポートしたものです。インポートして利用してください。

[{"id":"5554444f.0eef5c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"9d08eb63.43f558","type":"xlsx2json","z":"5554444f.0eef5c","name":"","filename":"","x":400,"y":200,"wires":[["a7441d02.0d52"]]},{"id":"6a087103.8934c","type":"debug","z":"5554444f.0eef5c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":790,"y":200,"wires":[]},{"id":"a7441d02.0d52","type":"split","z":"5554444f.0eef5c","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":590,"y":200,"wires":[["6a087103.8934c"]]},{"id":"b95f6ef4.47ec1","type":"http request","z":"5554444f.0eef5c","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://www.mext.go.jp/content/20201221-mxt_syogai03-000010378_1.xlsx","tls":"","persist":false,"proxy":"","authType":"","x":410,"y":140,"wires":[["9d08eb63.43f558"]]},{"id":"81f73788.75bb38","type":"inject","z":"5554444f.0eef5c","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":140,"wires":[["b95f6ef4.47ec1"]]}]

 簡単なフローですが、少しだけ説明します。

 Http-requestノードの設定を確認してみます。Returnの設定が”a binary buffer”になっていますね。ここはデフォルト値ではありませんので、注意してください。

 Xlsx2Jsonノードの設定を確認してみます。空っぽです。”Filename”に値が入力されていると、ファイルシステム上のファイルを探してしまいますので、ここには何も設定しないでください(”Name”は設定しても構いません)。

 Splitノードはデフォルト設定のまま接続していますので、特別な設定は不要です。

 フローの左端にある Injection ノードをクリックするとフローが動作します。Debugウィンドウに、JSONオブジェクトに変換された、Excelファイルが出力されれば成功です。

おわりに

 この記事では、Private Node を改良してバージョンアップする様子を記事にしてみました。 Xlsx2Json ノードには、まだまだ改良の余地があると思います。興味のある方は、さらに改良したバージョンの作成にチャレンジしてみてください。

 また、バージョン管理機能は、ファイルやフローの機能でも使うことができます。ぜひ、活用してみてください。