XMLデータをいい感じに javascript のオブジェクトに変換してくれる・・・という fast-xml-parser。
実際に使ってみた結果躓いた点を書き留めます。
以前にも同じようなことしていた。 Node.js で XML文書をパース
このときは XMLのパースに xmldom を使っていた。 完全に覚えていない。
プロジェクトディレクトリを作成して、そこに fast-xml-parser を入れておきます。
$ mkdir myxml
$ cd myxml
$ npm init -y
$ npm install fast-xml-parser --save
$ touch index.js
root 要素内に a, b, c 要素が出現する場合:
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const xml = `
<root>
<a>AAA</a>
<b>BBB</b>
<c>CCC</c>
</root>`;
const options = {
};
const parser = new XMLParser(options);
const object = parser.parse(xml);
console.log(object);
パースオプションはまだ空にしておく。
実行してみる。
$ node index.js
{ root: { a: 'AAA', b: 'BBB', c: 'CCC' } }
これは問題ない。
もし a 要素が複数出現したらどうなるのか? つまり、処理するXMLデータが以下の場合:
const xml = `
<root>
<a>AAA</a>
<b>BBB</b>
<c>CCC</c>
<a>AAA</a>
</root>`;
実行してみる。
$ node index.js
{ root: { a: [ 'AAA', 'AAA' ], b: 'BBB', c: 'CCC' } }
二回出現した a 要素がまとまってしまった。 データとしてはこの方が都合がよい場合もあるだろうけれど、 もし、HTML的な要素だとして h2, p, p, h2, p のような場合:
const xml = `
<root>
<h2>H2H2H2</h2>
<p>PPP</p>
<p>PPP</p>
<h2>H2H2H2</h2>
<p>PPP</p>
</root>`;
パースした結果は:
{ root: { h2: [ 'H2H2H2', 'H2H2H2' ], p: [ 'PPP', 'PPP', 'PPP' ] } }
あきらかに期待したものではない。
しかし、 preserveOrder というオプションがある。 それを使ってみる。
パースオプション他いろいろなオプションは以下を参照:
空にしていたオプションを以下のように修正:
const options = {
preserveOrder: true
};
実行するとこうなる。
[ { root: [ [Object], [Object], [Object], [Object], [Object] ] } ]
JSON.strigify() して内容がわかるようにする:
[{"root":[{"h2":[{"#text":"H2H2H2"}]},{"p":[{"#text":"PPP"}]},{"p":[{"#text":"PPP"}]},{"h2":[{"#text":"H2H2H2"}]},{"p":[{"#text":"PPP"}]}]}]
まあ、出現順は維持できるようになった。
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const xml = `
<root>
<a id="1">AAA</a>
<b id="2">BBB</b>
<c id="3">CCC</c>
</root>`;
const options = {
};
const parser = new XMLParser(options);
const object = parser.parse(xml);
console.log(object);
実行:
{ root: { a: 'AAA', b: 'BBB', c: 'CCC' } }
消える。
オプションで、属性を維持するように指定:
const options = {
ignoreAttributes: false,
attributeNamePrefix : '__',
};
実行:
{
root: {
a: { '#text': 'AAA', __id: '1' },
b: { '#text': 'BBB', __id: '2' },
c: { '#text': 'CCC', __id: '3' }
}
}
維持できました。
ちなみに、このオブジェクトを XML に戻すには:
new XMLBuilder(options).build(object);
コード全体では:
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const xml = `
<root>
<a id="1">AAA</a>
<b id="2">BBB</b>
<c id="3">CCC</c>
</root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : '__',
};
const parser = new XMLParser(options);
const object = parser.parse(xml);
//console.log(object);
const xml2 = new XMLBuilder(options).build(object);
console.log(xml2);
実行:
<root><a id="1">AAA</a><b id="2">BBB</b><c id="3">CCC</c></root>
パースしたときと同じオプションを XMLBuilder に指定しないと意図しない結果になるので注意が必要です。
こんなXMLをパースする場合。
<root>
<id>0123</id>
<id>456</id>
</root>
コード全体:
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const xml = `
<root>
<id>0123</id>
<id>456</id>
</root>`;
const options = {
};
const parser = new XMLParser(options);
const object = parser.parse(xml);
console.log(object);
const xml2 = new XMLBuilder(options).build(object);
console.log(xml2);
実行。
{ root: { id: [ 123, 456 ] } }
<root><id>123</id><id>456</id></root>
ある種のテキストノードは数値として扱われるので、0123 が 123 に変換されました。 0123 を維持することができません。
もちろん、パースオプション numberParseOptions を指定すればこれを回避できます。
const options = {
numberParseOptions: {
skipLike: /^[0-9]+/
}
};
この場合は、0,1,2,3...,9 で始める場合は数値にするのを阻止します。
このオプションをパースオプションに指定して実行。
{ root: { id: [ '0123', '456' ] } }
<root><id>0123</id><id>456</id></root>
0123 が維持できました。 (文字列として扱われています。)
こんなXML:
<root>
<p>Hello,<br/>World!</p>
</root>
HTMLでありそうな構造です。
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const xml = `
<root>
<p>Hello,<br/>World!</p>
</root>`;
const options = {
};
const parser = new XMLParser(options);
const object = parser.parse(xml);
console.log(object);
const xml2 = new XMLBuilder(options).build(object);
console.log(xml2);
実行。
{ root: { p: { br: '', '#text': 'Hello,World!' } } }
<root><p><br></br>Hello,World!</p></root>
Hello, と World! の間に改行があることを期待しているのに。 br要素が先頭に移動してしまった。
あきらかに期待した結果ではない。
これは、オプションで回避する方法がすぐわからなかった。 もちろん、このコードのようにパースしてXMLに戻すだけならば preserveOrder をオプション指定すればOKです。
const options = {
preserveOrder: true
};
これを XMLParser, XMLBuilder のオプションに指定すれば:
[ { root: [ [Object] ] } ]
<root><p>Hello,<br></br>World!</p></root>
問題ありません。
しかし、通常わざわざXMLをパースしたということは、 そこでなにがしかの処理をして・・・ということなので。 まあ、preserveOrder した上で、頑張ればいいってことだとは思う。