Kinx ライブラリ - XML

# XML

## News!

### Kinx - 3rd Preview Release!

これまでの修正も含め、3rd Preview Release を行いました。[ここで触れている Fiber の修正](https://qiita.com/Kray-G/items/1ca831b69df8222bc7bf) も含まれてます。

* https://github.com/Kray-G/kinx/releases

しかし未だ **プレビュー**。もし宜しければバグ報告等頂けると大変助かります。特に今回の XML はあまりテストできていない感満載。もうちょっとテストできてから紹介しようかとも思ったものの、せっかくのプレビュー版で使い方が分からないのもどうかと思い公開することにしました。実装自体はしてあり、サンプルは動くことを確認済です。

## はじめに

**「見た目は JavaScript、頭脳(中身)は Ruby、（安定感は AC/DC）」** でお届けしているスクリプト言語 [Kinx](https://github.com/Kray-G/kinx)。言語はライブラリが命。ということでライブラリの使い方編。 

今回は XML です。

* 参考
    * 最初の動機 ... [スクリプト言語 KINX（ご紹介）](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76)
        * 個別記事へのリンクは全てここに集約してあります。
    * リポジトリ ... [https://github.com/Kray-G/kinx](https://github.com/Kray-G/kinx)
        * Pull Request 等お待ちしております。 

XML もよく利用するのでスクリプト言語でサクッと扱いたい要素の一つ。

## XML

### DOMパース

`Xml.parseFile()` または `Xml.parseString()` を使って DOM ツリーを構築する。ファイルを読み込む場合は以下の通り。

```javascript
var doc = Xml.parseFile("xmlfile.xml");
```

下記は文字列を直接パースする例。

```xml
var doc = Xml.parseString(%{
<?xml version="1.0" encoding="UTF-8" ?>
<artists>
  <artist country="US" id="1">
    <name>BON JOVI</name>
    <price>2400</price>
    <img file="bonjovi.jpg"/>
  </artist>
  <artist country="US" id="2">
    <name>GUNS N ROSES</name>
    <price>21000</price>
    <img file="GNR.jpg"/>
  </artist>
  <artist country="DE" id="3">
    <name>Helloween</name>
    <price>2400</price>
    <img file="helloween.jpg"/>
  </artist>
</artists>
});
```

返されたドキュメント・オブジェクトは以下のメソッドを持つ。

|                  メソッド                   |                     内容                     |
| ------------------------------------------- | -------------------------------------------- |
| `documentElement()`                         | ルートドキュメントを取得                     |
| `createElement(tagname)`                    | Element ノードを作成する                     |
| `createTextNode(text)`                      | Text ノードを作成する                        |
| `createComment(comment)`                    | Comment ノードを作成する                     |
| `createCdataSection(content)`               | CDATA Section ノードを作成する               |
| `createProcessingInstruction(target, data)` | Processing Instruction ノードを作成する      |
| `createEntityReference(name)`               | Entity Reference ノードを作成する            |
| `createElementNS(nsUri, qname)`             | Element ノードを名前空間を指定して作成する   |
| `getElementById(id)`                        | id を指定してノードを検索する                |
| `getElementsByTagName(tagName)`             | tagName のノードを配列にして返す             |
| `xpath(expr)`                               | expr の XPATH を評価し、結果を配列にして返す |

### ルートノード

ルートノードは `documentElement()` メソッドを使用して以下のように取得する。

```javascript
var root = doc.documentElement();
```

#### XMLノード

ルートノードを含む XML ノードは以下のプロパティとメソッドを持つ。

##### プロパティ

|   プロパティ   |      内容      |
| -------------- | -------------- |
| `type`         | ノード種別     |
| `name`         | QName          |
| `tagName`      | タグ名         |
| `localName`    | ローカル名     |
| `namespaceURI` | 名前空間 URI   |
| `prefix`       | プレフィックス |

##### メソッド

|               メソッド                |                     内容                     |
| ------------------------------------- | -------------------------------------------- |
| `attributes()`                        | 属性一覧を配列で返す。                       |
| `setAttribute(qname, value)`          | 属性を設定する。                             |
| `setAttributeNS(nsUri, qname, value)` | 名前空間を指定して属性を設定する。           |
| `removeAttribute(qname)`              | 属性を削除する。                             |
| `removeAttributeNS(nsUri, localName)` | 名前空間を指定して属性を削除する。           |
| `parentNode()`                        | 親ノードを返す。                             |
| `children()`                          | 子ノードを配列で返す。                       |
| `firstChild()`                        | 最初の子ノードを返す。                       |
| `lastChild()`                         | 最後の子ノードを返す。                       |
| `nextSibling()`                       | 次のノードを返す。                           |
| `previousSibling()`                   | 前のノードを返す。                           |
| `appendChild(node)`                   | 子ノードにノードを追加する。                 |
| `removeChild(node)`                   | 子ノードからノードを削除する。               |
| `replaceChild(node1, node2)`          | 子ノードのノードを置き換える。               |
| `replaceNode(node)`                   | 自分自身のノードを別のノードでを置き換える。 |
| `insertBefore(node)`                  | 前のノードとしてノードを追加する。           |
| `insertAfter(node)`                   | 次のノードとしてノードを追加する。           |
| `remove()`                            | ノードを削除する。                           |
| `textContent()`                       | テキストを取得する。                         |
| `innerText()`                         | テキストを取得する。                         |
| `hasChildren()`                       | 子ノードが存在すれば 1 を返す。              |
| `hasAttributes()`                     | 属性があれば 1 を返す。                      |
| `getElementById(id)`                  | id を指定してノードを検索する                |
| `getElementsByTagName(tagName)`       | tagName のノードを配列にして返す             |
| `xpath(expr)`                         | expr の XPATH を評価し、結果を配列にして返す |

### XPath

XPath は XPATH 式にマッチしたノードをノードセット（配列）の形で返す。ノードセットにも `xpath()` メソッドがあり、絞り込んだノード群に対し XPATH をチェインして使うことができる。

先ほどのサンプル XML のドキュメントに対して実行。

```javascript
var nodes = doc.xpath("//artist")
               .xpath("price")
               .map(&(p) => p.innerText());

nodes.each(&(text) => {
    System.println(text);
});
```

結果。

```
2400
21000
2400
```

## サンプル・ソース

同梱しているサンプルソースを載せておきます。説明していない `Xml.Writer` とかありますが、こんな感じの DOM パースができる例ということで、参考になると思い。

```javascript
function displayXml(doc, node, indent) {
    System.print("  " * indent);
    if (node.type == Xml.ELEMENT_NODE) {
        System.print("ELEM %s" % node.name);
    } else if (node.type == Xml.TEXT_NODE) {
        System.print("TEXT %s" % node.value.trim());
    }

    var attr = node.attributes();
    for (var i = 0, len = attr.length(); i < len; ++i) {
        System.print("[%s=%s]" % attr[i].name % attr[i].value);
    }
    System.println("");

    var child = node.firstChild();
    while (child) {
        displayXml(doc, child, indent + 1);
        child = child.nextSibling();
    }
}

var doc = Xml.parseString(%{
<?xml version="1.0" encoding="UTF-8" ?>
<artists>
  <artist country="US" id="1">
    <name>BON JOVI</name>
    <price>2400</price>
    <img file="bonjovi.jpg"/>
  </artist>
  <artist country="US" id="2">
    <name>GUNS N ROSES</name>
    <price>21000</price>
    <img file="GNR.jpg"/>
  </artist>
  <artist country="DE" id="3">
    <name>Helloween</name>
    <price>2400</price>
    <img file="helloween.jpg"/>
  </artist>
</artists>
});
var root = doc.documentElement();
displayXml(doc, root);

var el = root.getElementById("3");
if (el) {
    el.remove();
}

System.println("");
System.println("getElementByTagName:");
var els = root.getElementsByTagName("img");
if (els.isArray) {
    els.each(&(el) => displayXml(doc, el));
}

System.println("");
System.println("XPath:");
var nodes = doc.xpath("//artist").xpath("price");
if (nodes.isArray) {
    nodes.each(&(el) => displayXml(doc, el));
}

var xmlWriter = new Xml.Writer(System);
xmlWriter.write(doc);
xmlWriter.write(root);
```

実行結果。

```
ELEM artists
  TEXT
  ELEM artist[country=US][id=1]
    TEXT
    ELEM name
      TEXT BON JOVI
    TEXT
    ELEM price
      TEXT 2400
    TEXT
    ELEM img[file=bonjovi.jpg]
    TEXT
  TEXT
  ELEM artist[country=US][id=2]
    TEXT
    ELEM name
      TEXT GUNS N ROSES
    TEXT
    ELEM price
      TEXT 21000
    TEXT
    ELEM img[file=GNR.jpg]
    TEXT
  TEXT
  ELEM artist[country=DE][id=3]
    TEXT
    ELEM name
      TEXT Helloween
    TEXT
    ELEM price
      TEXT 2400
    TEXT
    ELEM img[file=helloween.jpg]
    TEXT
  TEXT

getElementByTagName:
ELEM img[file=bonjovi.jpg]
ELEM img[file=GNR.jpg]

XPath:
ELEM price
  TEXT 2400
ELEM price
  TEXT 21000
<artists>
        <artist country="US" id="1">
                <name>BON JOVI</name>
                <price>2400</price>
                <img file="bonjovi.jpg" />
        </artist>
        <artist country="US" id="2">
                <name>GUNS N ROSES</name>
                <price>21000</price>
                <img file="GNR.jpg" />
        </artist>
</artists>
<artists>
        <artist country="US" id="1">
                <name>BON JOVI</name>
                <price>2400</price>
                <img file="bonjovi.jpg" />
        </artist>
        <artist country="US" id="2">
                <name>GUNS N ROSES</name>
                <price>21000</price>
                <img file="GNR.jpg" />
        </artist>
</artists>
```

## おわりに

XPath が使えると便利だ。

そして、XML と [Zip](https://qiita.com/Kray-G/items/f8be2c7eabbfc8731ff5)（以前の記事）を組み合わせると、実は Xlsx ファイル（Excel ファイル）の読み書きができたりします。Xlsx ファイルは Office Open XML という名前で標準仕様化されており（色々問題もあるが）、XML ファイルを Zip で固めたものになってるので読めたりするといった具合。

ただ、実際問題として Office Open XML の全てをサポートするってのは相当量のコードになるので、すぐにできそうなのは簡易的な読み書きくらいですね。時間があればチャレンジしよう。

ではまた次回。