XML 文書と DOM

DOM (Document Object Model)

DOM (Document Object Model; どむ) は、 プログラムから XML 文書のような構造化された文書(document)を扱うための、文書のモデルです。 特定のプログラミング言語に依存しない仕様となっています。

Java のライブラリは DOM Level 3 の仕様に準拠しています。

DOMツリーの構成要素

DOM では、要素(element)やテキスト(text)といった文書の構成要素を、 木構造のノード(node)として扱います。

主なノード(Node)を以下に挙げます。

Document
文書全体を表すノード。子ノードとして1つの Element を持つことができます。
Element
要素を表すノード。0個以上の子ノードを持ちます。
Text
テキストを表すノード。子ノードは持ちません。
CDATASection
テキストを表すノード。マークアップの解釈がされないテキスト(CDATA)なため、HTMLなどのタグ付きテキストをそのまま入れることができる。
Attr
要素の属性(attribute)を表すノード。実際には要素の子ノードではないため、DOMツリーには現れない。

注: DOM4 では CDATA は特別扱いされなくなり Text ノードとなります。また、属性はノードではなくなります。

以下の XML 文書を考えます。

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <item>
      <title>りんご</title>
      <link>http://www.apple.com/jp/</link>
    </item>
  </channel>
</rss>

apple.xml

この XML文書を DOM tree で表現すると、以下のようになります。

要素(Element)だけでなく、テキスト(Text)もノードになっていることに着目してください。 例えば title 要素の内容(文字列)は、title 要素の子ノードであるテキストの値となります。

なお、実際には字下げのための空白や改行もテキストとして扱われるため、それが DOM tree に反映されます。

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
■■<item>
■■■<title>りんご</title>
■■■<link>http://www.apple.com/jp/</link>
■■</item>
</channel>
</rss>

ここまでに、Node, Document, Element, Text が登場しました。 DOM の仕様では、これらに対してどのような操作ができるのかが規定されています。 Java では、org.w3c.dom 以下のパッケージのインタフェースとして用意されています。

なお、これら DOM のインタフェースとは別に、 javax.xml 以下の各種パッケージに XML 全般のためのクラスが用意されています。

DOM プログラミング

DOM を用いるプログラムでは、メモリ上に DOMツリーを構築し、それに対して操作を行います。

  1. DOM ツリーの生成: XML文書を読み込むか新規にメモリ上に生成。
  2. DOM ツリーの操作: 参照、書き換えなど。
  3. DOM ツリーの書き出し: XML文書として書き出し。

ここでは、XML文書を読み込んでツリーを探索する方法を学びます。 以下の XML文書を読み込んでみます。

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <item>
      <title>りんご</title>
      <link>http://www.apple.com/jp/</link>
    </item>
    <item>
      <title>みかん</title>
      <link>https://orangeamps.com/</link>
    </item>
    <item>
      <title>バナナ</title>
      <link>https://en.wikipedia.org/wiki/The_Velvet_Underground_%26_Nico</link>
    </item>
  </channel>
</rss>

fruits.xml

eclipse を使っている場合には、プロジェクトのフォルダの直下に置いてください。

DOMツリーの生成

ここではまず、ファイルから読み込む場合を見てみましょう。

DocumentLoaderFromFile.java

登場する主なクラス/インタフェースを以下に挙げます。

Document
メモリ上の DOM ツリー全体を表します。
DOMImplementationLS
読み込み(Load)と保存(Save)のためのDOMの実装(implementation)を表します。DOMImplementationRegistry によりインスタンスを生成します。
LSInput
入力対象を表します。
LSParser
構文解析器(parser)を表します。

定型的な書き方ですが、 DOM ツリーを構築する際の入力として InputStream を与えることに着目しましょう。 ファイルを渡すことも、Webから取得したデータを渡すこともできます。

DocumentLoader.java

Document の getDocumentElement() を用いると、DOM ツリーの root要素を得ることができます。 このサンプルプログラムでは root要素の要素名を表示しているだけです。 ツリーをたどって情報を得る方法は次の節で学びます。

DOMツリーの操作

root要素からツリーをたどるには、ノード(Node)に対する操作を知る必要があります。

Node

ノードに関する主な情報として、以下の3つがあります。

NodeType
要素、テキストといったノードの種類です。

NodeName
ノード名です。要素であれば要素名です。テキストの場合は #text という固定の名前です。
NodeValue
ノード値です。テキストの場合はその内容(文字列)です。要素の場合は null です。

これらの情報は、それぞれ getNodeType(), getNodeName(), getNodeValue() というメソッドにより得ることができます。

NodeType を表す定数の主なものを以下に挙げます。

Node.ELEMENT_NODE
要素
Node.ATTRIBUTE_NODE
属性
Node.TEXT_NODE
テキスト
Node.CDATA_SECTION
CDATA section (タグ付きテキストをそのまま書きたいときなどに利用)

属性もノードとして扱われます(DOM3までは)。

ツリーをたどるには

root要素から子ノードをたどるには、Node に対する以下の操作を使います。

Node getFirstChild()
子ノードのうち、先頭にあるものを返します。
Node getNextSibling()
自分と同じ階層にある、自分の次のノードを返します。次のノードがない場合には null を返します。

XMLでは、要素の出現順序に意味があったことを思い出しましょう。 子ノードは、同じ階層でも順序が決まっていますので、先頭から順に見ていくといった操作をすることができます。ノード node の子ノードをすべてたどる繰り返しは以下のように書けます。

	for(Node current = node.getFirstChild();
		current != null;
		current = current.getNextSibling())

DocumentViewer.java

子ノードを順にたどる際、着目しているノードが要素の場合にはその子ノードも考えられるため、 子ノードをたどるメソッドを再帰的に呼び出すことにより、ツリー全体を走査することができます。 深さ優先探索となります。