Private Node でExcel → JSON変換ノードを作ってみる

 9/21のアップデートで、それまで有料だった Private Node 機能がFreeプランでも使用できるようになりました。この記事では、 Private Node 機能を使って、ExcelファイルをJSONオブジェクトに変換するノードを作ってみます。

目次

はじめに

 この記事で作るものは、Excelファイルを読み込んで、JSONオブジェクトを出力(変換)するPrivate Nodeです。

 記事「設定ファイルをIoTデバイスにデプロイする方法」では、オープンデータAPIを利用して、新型コロナウィルス感染症の感染者数データを取得しました。この記事を書くために、デジタル庁が運営するデータカタログサイト “data.go.jp”を眺めていたのですが、現状ではPDF形式やExcel形式(xls, xlsx共)のデータがまだまだありますね。

 せっかく公開されているデータも取り扱えないと、役に立てることができません。そこで、ExcelファイルをJSONオブジェクトに変換するノードを作ろうと思いつきました。

xlsxモジュール

 ExcelファイルをJSONオブジェクトに変換するといっても、一から作るのは容易ではありません。

 しかし、Node-REDユーザーには強い味方 “NPM” があります。ここで、”Excel” を検索してみると、 “xlsx” というモジュールが見つかりました(他にもたくさんのExcelを扱うモジュールが見つかります。Parser機能を持つ人気がありそうなモジュールを選んでいます)。

 ドキュメントを読むと便利に使えそうです。本記事では、このxlsxモジュールを利用してノードを作ってみます。

Private Node 作成の注意事項

 enebularのPrivate Nodeの作り方は、基本的にはNode-REDのノードの作り方と同じです。enebularのマニュアルには注意点として、以下の4点があげられています。

  • パッケージ名(package.jsonに記載するnameの値)はenebular-privatenode-contrib-から始まること
  • プロジェクトに同名のパッケージ名のPrivate Nodeが登録されていないこと
  • パッケージファイルのサイズは2MB以下であること
  • Private NodeのNode Typeと同名のNode TypeがNode-REDのパレットに存在しないこと

 以上を踏まえて、今回作るノードの名称を以下のように決めます。ノードタイプは、エディタ上で見えるノードの名前のことです。

モジュール名: enebular-privatenode-contrib-xlsx2json
ノードタイプ:xlsx2json

 開発の準備として、Node.js、npmコマンド、そしてもちろんNode-REDのインストールが必要ですが、ここではリンクを紹介するだけで省略させていただきます。

  • Node.jsダウンロードページ … 自身の開発環境に合わせてインストーラーをダウンロードしてインストールしてください。npmコマンド(Node Package Manager)はNode.jsをインストールすると一緒にインストールされているはずです
  • Node-REDのインストール … 環境毎のNode-REDのインストール方法が説明されています

Private Node の雛形を作る

 では、ノードを作り始めます。まずは、開発用のフォルダを作ります。

mkdir enebular-privatenode-contrib-xlsx2json
cd enebular-privatenode-contrib-xlsx2json

 作ったフォルダに入って、最初の設定を行います。

npm init

 以下のような感じで設定を行います。

package name: (enebular-privatenode-contrib-xlsx2json) enebular-privatenode-contrib-xlsx2json
version: (1.0.0) 0.1.0
description: This module convert Excel files from binary to json object.
entry point: (index.js) 
test command: 
git repository: 
keywords: Node-RED, enebular, Excel, JSON
license: (ISC) 
About to write to /hoge/hige/huge/enebular-privatenode-contrib-xlsx2json/package.json:

{
  "name": "enebular-privatenode-contrib-xlsx2json",
  "version": "0.1.0",
  "description": "This module convert Excel files from binary to json object.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Node-RED",
    "enebular",
    "Excel",
    "JSON"
  ],
  "author": "Fuga Fuga <fuga.fuga@funga.jp>",
  "license": "ISC"
}

Is this OK? (yes) 

 最初のpackage.jsonファイルが出来上がります。

 さらに、package.jsonに "node-red"キーを追加する必要があります。

{
  "name": "enebular-privatenode-contrib-xlsx2json",
  ...省略...
  "license": "ISC",
  "node-red": {
      "nodes": {
          "xlsx2json": "xlsx2json.js"
      }
  }
}

 次に、xlsx モジュールをインストールします。enebular-privatenode-contrib-xlsx2json フォルダ内で以下のようにコマンドを実行してください。--saveオプションは忘れずに!

 これで、xlsxモジュールが追加され、さらにpackage.jsondependenciesにxlsxモジュールが追加されます。

npm install xlsx --save

 次に、同じフォルダにHTMLファイル xlsx2json.html とJavaScriptファイル xlsx2json.js を作成します。

 Node-REDのドキュメント ノードの開発・初めてのノード開発 には、親切にも雛形になるHTMLファイル(lower-case.html)とJavaScript(lower-case.js)のコードが記述されています。xlsx2json.htmlxlsx2json.js の中身は、一旦、この雛形のコピーにしてしまいましょう。

HTMLファイルを作る

 HTMLファイルから作ります。

 先程のコピーで、xlsx2json.htmlの中身は以下のようになっているはずです。

<script type="text/javascript">
    RED.nodes.registerType('lower-case',{
        category: 'function',
        color: '#a6bbcf',
        defaults: {
            name: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"lower-case";
        }
    });
</script>

<script type="text/html" data-template-name="lower-case">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
</script>

<script type="text/html" data-help-name="lower-case">
    <p>A simple node that converts the message payloads into all lower-case characters</p>
</script>

 では、xlsx2json用に、これを改造していきます。

 まず、lower-caseというノード名を全て xlsx2json に変更します。4箇所ありますから漏れないように注意してください。

 次にノードのプロパティを設定していきます。category'function'から'parser'に変更しましょう。color'parser'カテゴリーの他のノードに合わせて、'#DEBD5C'に変更しましょう。defaultsでは、変換するExcelファイルのファイル名(パス)を設定するfilenameを追加します。以下のように書き換えます。

        defaults: {
            name: {value:""},
            filename: {value:""}
        },

 次にノードの編集ダイアログ(data-template-name="xlsx2json"のところ)を修正します。以下の4行を追加してください。これで、Node-REDのエディタ画面で、このノードの編集ダイアログを開くとFilenameが編集できるようになります。

    <div class="form-row">
        <label for="node-input-filename"><i class="fa fa-tag"></i> Filename</label>
        <input type="text" id="node-input-filename" placeholder="Filename">
    </div>

 最後にヘルプメッセージ(data-help-name="xlsx2json"のところ)を編集します。

    <p>A simple node that converts a Excel files from binary to json object.</p>

 最終的には以下のようなHTMLファイルになります。

<script type="text/javascript">
    RED.nodes.registerType('xlsx2json',{
        category: 'parser',
        color: '#DEBD5C',
        defaults: {
            name: {value:""},
            filename: {value:""}
        },
        inputs:1,
        outputs:1,
        icon: "file.png",
        label: function() {
            return this.name||"xlsx2json";
        }
    });
</script>

<script type="text/html" data-template-name="xlsx2json">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
    <div class="form-row">
        <label for="node-input-filename"><i class="fa fa-tag"></i> Filename</label>
        <input type="text" id="node-input-filename" placeholder="Filename">
    </div>
</script>

<script type="text/html" data-help-name="xlsx2json">
    <p>A simple node that converts a Excel files from binary to json object.</p>
</script>

JavaScriptファイルを作る

 いよいよJavaScriptファイルを作ります。

 先程のコピーで、xlsx2json.jsの中身は以下のようになっているはずです。

module.exports = function(RED) {
    function LowerCaseNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            msg.payload = msg.payload.toLowerCase();
            node.send(msg);
        });
    }
    RED.nodes.registerType("lower-case",LowerCaseNode);
}

 まずは、ノードの名前を変更しましょう。最後の行の"lowar-case""Xlsx2Json"にします

 また、関数名LowerCaseNodeXlsx2JsonNodeに変更しましょう。これは2箇所に出てきますので、両方修正してください。2回目のところは、Node-REDにノードを登録する処理です。

 次にxlsxモジュールを使えるように、1行目でrequireしておきましょう。

const XLSX = require('xlsx');

module.exports = function(RED) {
    ...
}

 エディタで設定するファイル名(Filename)を取得します。先ほど、HTMLファイルで設定しましたね。

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

 config.filename にファイル名が入っています(エディタで設定していれば)。

 いよいよ、Xlsx2Jsonノードにmsgが入力された時の処理を書きます。node.onの処理を丸ごと書き換えます。

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();
            }
        });

 以下の2行が、Excelファイルを開いて、JSONオブジェクトに変換する処理です。xlsxモジュールが実行してくれるのでたった2行です。

const workbook = XLSX.readFile(node.filename);
msg.payload = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

 最終的なファイルは以下のようになります。

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);
}

enebularに Private Node を登録する

 ここで、一旦テストをしたいところですが、ちゃんと動作すると信じてPrivate Node化しましょう。(Node-REDのドキュメント はじめてのノード開発 には「Node-REDでノードの動作確認」という章があって、動作確認の方法を教えてくれます。そちらを参照してください!!)

 ここからは、基本的にはenebularのマニュアルに従うだけです。enebular-privatenode-contrib-xlsx2json フォルダ内で以下のようにコマンドを実行してください。

npm pack

 enebular-privatenode-contrib-xlsx2json-0.1.0.tgz というファイルができているはずです。このファイルをenebularに登録します。

 enebularにSign-Inして、プロジェクトを開きます(好きなプロジェクトを開いてください)。右下の+ボタンを押して、Create Asset ダイアログを開きます(下図)。Asset Typeprivatenodeにします。アセット名はなんでも良いのですが、ここでは xlsx2json としましょう。また、ここでは一旦、DescriptionDefault Privilegeはデフォルトのままとします。

 最後に、「Drag and drop your Private Node File(tgz) or select it」と書かれた部分に、先ほどの enebular-privatenode-contrib-xlsx2json-0.1.0.tgz ファイルをドラッグ&ドロップします。これで準備は完了です。ダイアログ下部のUploadボタンを押してください。

 以上で、Private Nodeの登録は完了です。

 これ以降は、このプロジェクトで作るフローには、登録したXlsx2Jsonノードが使えるようになります。

使ってみる

 では、いよいよPrivate Node "Xlsx2Json"を使ってみましょう。

 Private Nodeを登録したenebularのプロジェクトの中に、フローを新規作成します。フローの名前は何でも良いのですが、ここではxlsx2json-flowという名前にします。フローエディタを開きます。

 ノードのリストに、xlsx2jsonノードが登録されているはずです。

Xlsx2Json Privatenode

 このノードを使って作るのは以下のフローです。

UseCase flow for Xlsx2Json Privatenode

 以下のJSONファイルをインポートすると、上図のフローがインポートできます。

[{"id":"5554444f.0eef5c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"9d08eb63.43f558","type":"xlsx2json","z":"5554444f.0eef5c","name":"","filename":"test.xlsx","x":400,"y":200,"wires":[["a7441d02.0d52"]]},{"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"]]},{"id":"4bf6c22a.c0056c","type":"debug","z":"5554444f.0eef5c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":790,"y":140,"wires":[]},{"id":"de2e8028.7ff2a","type":"inject","z":"5554444f.0eef5c","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":200,"wires":[["9d08eb63.43f558"]]},{"id":"d5b5a5c4.cd4608","type":"file","z":"5554444f.0eef5c","name":"","filename":"test.xlsx","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":600,"y":140,"wires":[["4bf6c22a.c0056c"]]},{"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":"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":[["d5b5a5c4.cd4608"]]},{"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"]]}]

 

 このフローでは以下の2つを行うことができます。

  1. data.go.jpに登録されていた「オープンデータ・教材リスト情報」をダウンロードして、"test.xlsx"というファイルにして保存する
  2. 保存した"test.xlsx"ファイルを読み込んで、Xlsx2JsonノードでJSONオブジェクトに変換して、Debugノードに出力する

 1の処理が画像の上の列の処理、2の処理が画像の下の列の処理になります。

 2つの列ともに、左端にInjectノードがあります。エディタを開いて、Injectノードを押すとフローが実行されます。上の列から順番に実行してください。 Debugウィンドウにそれぞれの実行結果が出力されます。

おわりに

 この記事では、enebularのPrivate Nodeとして Xlsx2Json ノードを作ってみました。xlsxモジュールを使うことで、とても簡単に新しいノードが作れることがわかっていただけたのではないかと思います。

 オリジナルのノードの開発というと、とてもハードルが高く感じられるかもしれません。ところが、この記事のノード開発では、xlsxモジュールを見つけてくるところと、使い方を理解するところが一番大変だったのです。xlsxモジュールはよくできているモジュールなので、ここまで来れば後は出来たも同然です。

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