Tech Racho エンジニアの「?」を「!」に。
  • 開発

JScriptでXMLを操作するスクリプトをNode.js+xmldomに移植する

はじめまして。Glx64xと申します。

突然ですが、JScript+MSXMLって便利ですよね!
XMLをちょっといじりたいとき、Windows標準機能だけでJavaScriptの文法でお手軽にDOM操作ができるのは非常にありがたいです。
Wineが入っていれば、Wineにもcscript.exeとjscript.dll(SpiderMonkeyベースらしい)が同梱されているため、Windows以外の環境でも割と動いちゃいます。

しかし、そんなJScriptも今や古い技術。
UTF-8のような文字コードは扱いにくいのが現状です。
また、もしこれをサーバー上で使う場合、WindowsサーバーならまだしもLinuxサーバー上でWineを動かすのは抵抗があります。

そこで、今回は簡単なスクリプトを例に、JScriptで作成したスクリプトをNode.js+xmldomに移植してみたいと思います。

なぜxmldom?

おおむねW3C標準ベースで、使い方が調べやすいと思ったからです。

xmldomの導入

Node.js及びnpmの導入方法は割愛します。

npm install xmldom

また、Shift_JISなど、UTF-8以外の文字コードを扱う場合、iconv-liteもあると便利です。

npm install iconv-lite

サンプルコード

今回はサンプルのXMLとして、Inkscapeで作成したものをテキストエディタでShift_JISに変換した以下のSVGを用意しました。(SVGの中身はXMLです。)
(TechRachoにはSVGを張ることができないため、pngに変換してあります。)

JScriptを使って画像中の「こんにちは」という文字を「こんばんは」に書き換え、該当する要素のIDを表示するスクリプトを以下に示します。

移植前(JScript)のコード

"use strict";

var dom = new ActiveXObject("MSXML2.DOMDocument");
dom.load("sample_sjis.svg");

if (dom.parseError.errorCode === 0) {
    var tspans = dom.documentElement.getElementsByTagName("tspan");

    for (var i = 0; i < tspans.length; i++) {
        if (tspans[i].text === "こんにちは") {
            WScript.Echo(tspans[i].getAttribute("id"));
            tspans[i].text = "こんばんは";
        }
    }
    dom.save("output.svg");
} else {
    // Error
}

実行結果

tspan5133

移植

では、このコードをNode.js+xmldomに移植していきましょう。
まず変更した箇所だけを見ていきます。

ファイルの読み込み

JScript

var dom = new ActiveXObject("MSXML2.DOMDocument");
dom.load("sample_sjis.svg");

Node.js+xmldom

// パッケージ/モジュールの読み込み
var fs = require("fs");
var DOMParser = require("xmldom").DOMParser;
var iconv = require("iconv-lite");

// ファイルの読み込み
var file = fs.readFileSync("sample_sjis.svg");
var text = iconv.decode(file, "Shift_JIS");

// 読み込んだファイルをDOMParserにかける
var dom = new DOMParser().parseFromString(text, "application/xml");

一気に長くなりましたが、長くなるのは基本最初のモジュールやファイルの読み込みと最後のファイル書き込みだけですのでご安心ください。

JScriptではDOMDocumentにloadメソッドが生えていますが、Node.js+xmldomの場合fs.readFileSyncを使ってファイルを読み込む感じですね。

iconv-liteについては、対象のXMLファイルがUTF-8なら必要ありません。

エラー判定

JScript

if (dom.parseError.errorCode === 0) {

Node.js+xmldom

if (dom.documentElement !== null) {

MDNの説明だとDOMParserはエラーのときエラードキュメントを返すようですが、xmldomのDOMParserからはnullが返ってくるようです。

テキストノードの取得/設定と標準出力

JScript

if (tspans[i].text === "こんにちは") {
    WScript.Echo(tspans[i].getAttribute("id"));
    tspans[i].text = "こんばんは";
}

Node.js+xmldom

if (tspans[i].textContent === "こんにちは") {
    console.log(tspans[i].getAttribute("id"));
    tspans[i].textContent = "こんばんは";
}

JScriptでは要素.text、xmldomでは要素.textContentでテキストノードにアクセスできます。

また、標準出力はJScriptではWScript.Echo(cscript.exeで実行している場合)ですが、Node.jsではconsole.logです。

Node.jsのほうはブラウザとおなじですね。

ファイル書き出し

JScript

dom.save("output.svg");

Node.js+xmldom

fs.writeFileSync("output.svg",
                 iconv.encode(dom, "Shift_JIS"),
                 {encoding: null});

JScriptではDOMDocumentにsaveメソッドが生えていますが、Node.js+xmldomの場合fs.writeFileSyncに出力するオブジェクトを渡してあげる感じです。

移植後(Node.js+xmldom)のコード全体

"use strict";

var fs = require("fs");
var DOMParser = require("xmldom").DOMParser;
var iconv = require("iconv-lite");

var file = fs.readFileSync("sample_sjis.svg");
var text = iconv.decode(file, "Shift_JIS");

var dom = new DOMParser().parseFromString(text, "application/xml");

if (dom.documentElement !== null) {
    var tspans = dom.documentElement.getElementsByTagName("tspan");

    for (var i = 0; i < tspans.length; i++) {
        if (tspans[i].textContent === "こんにちは") {
            console.log(tspans[i].getAttribute("id"));
            tspans[i].textContent = "こんばんは";
        }
    }
    fs.writeFileSync("output.svg",
                     iconv.encode(dom, "Shift_JIS"),
                     {encoding: null});
} else {
    // Error
}

実行結果

tspan5133

まとめ

基本的にxmldomについては、公式からもリンクが貼られているとおりMDNを参照すると大体の情報は見つかりそうです。
JScriptもNode.js+xmldomも、XMLを扱うときには便利なツールとなっています。
今回の記事で扱ったように移植は簡単なので、とりあえずどちらかで書いて、不都合が生じたら書き直すというのもありだと思います。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。