大量のXML文書から InDesign 文書を生成する必要が生じたので、Node.js での XMLパース方法をメモ。
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文書の取り扱いは楽な気がします。