Thursday, April 23, 2020 / Javascript, Node.js, XML

Node.js で XML文書をパース

大量のXML文書から InDesign 文書を生成する必要が生じたので、Node.js での XMLパース方法をメモ。

xmldom を使う

xmldomを使います。

まずプロジェクトディレクトリの作成とプロジェクトの初期化, および xmldom をインストール:

mkdir parsexml
cd parsexml
npm init -y
npm install --save xmldom

テスト用のXML文書は以下の通り、test.xml として保存:

<?xml version="1.0" encoding="utf-8"?>
<list>
    <item>
        <name>MacBook Pro</name>
        <since>Dec. 2016</since>
    </item>
    <item>
        <name>MacBook</name>
        <since>Jul. 2018</since>
    </item>
    <item>
        <name>MacBook Air</name>
        <since>Mar. 2020</since>
    </item>
</list>

手始めに、テキストを読み込んで Document のインスタンスを得る index.js :

const fs = require('fs');
const DOMParser = require('xmldom').DOMParser;

const text = fs.readFileSync('test.xml', 'utf8');
const doc = new DOMParser().parseFromString(text);
console.log(doc);

doc.documentElement すれば ルートの要素つまり list 要素 が、 doc.documentElement.childNodes すれば item 要素の NodeList が得られる:

const items = doc.documentElement.childNodes;
for(let i=0; i<items.length; i++){
    const item = items[i];
    console.log(`- ${item.tagName} / ${item.constructor.name}`);
}

実行 node index.js してみると:

- undefined / Text
- item / Element
- undefined / Text
- item / Element
- undefined / Text
- item / Element
- undefined / Text

item 要素(Element) だけがくるかと思ったのですが、Text が混じります。

今は Element だけに関心があるので、 item.constructor.name が "Element" となるものだけを使うことにします。 それから doc.documentElement.childNodes で得た NodeList インスタンスに対して forEach が使えないので、 toList というヘルパー関数を書いて、 forEach できるようにします:

const toList = (items)=> {
    const list = []
    for( let i=0; i<items.length; i++ ){
        list.push(items[i]);
    }
    return list.filter( (item)=>item.constructor.name==='Element' )
}

これで、 toList(doc.documentElement.childNodes).forEach できるようになりました。
あとは、 item 要素内のサブ要素 name, since 要素をパースできる関数を書きます:

const parseItemElement = (item)=>{
    const subItems = item.childNodes;
    toList(subItems).forEach( (subItem)=> {
        const value = subItem.childNodes[0].nodeValue;
        console.log(`  - ${subItem.tagName} : ${value}`);
    });
};

全部つなげると index.js は以下の通り:

const fs = require('fs');
const DOMParser = require('xmldom').DOMParser;

const toList = (items)=> {
    const list = []
    for( let i=0; i<items.length; i++ ){
        list.push(items[i]);
    }
    return list.filter( (item)=>item.constructor.name==='Element' )
}

const parseItemElement = (item)=>{
    const subItems = item.childNodes;
    toList(subItems).forEach( (subItem)=> {
        const value = subItem.childNodes[0].nodeValue;
        console.log(`  - ${subItem.tagName} : ${value}`);
    });
};


const text = fs.readFileSync('test.xml', 'utf8');
const doc = new DOMParser().parseFromString(text);

const items = doc.documentElement.childNodes;
toList(items).forEach( (item)=>{
    const isElement = (item.tagName!=null);
    if( isElement ){
        console.log(`- ${item.tagName}`);
        parseItemElement(item);
    }
});

node index.js した結果:

- item
  - name : MacBook Pro
  - since : Dec. 2016
- item
  - name : MacBook
  - since : Jul. 2018
- item
  - name : MacBook Air
  - since : Mar. 2020

になりました。

まとめ

今まで JavaのJDOMばかり使ってきましたが、それより node.js + xmldom によるXML文書の取り扱いは楽な気がします。