revised: Oct./26th/03
DOM (Document Object Model) はW3Cが勧告しているXMLのAPIです。プログラム言語によらず、汎用的な仕様となっています。
DOM 自体が巨大な仕様なので、DOM Level 2からは複数の仕様に分割されて勧告されています。
現在、DOMツリーの保存や読み込み(シリアライゼーション)、XPathに対応した Level 3が策定中です。DOM level 3 XPathが勧告間近の勧告候補 (Candidate Recommendations)となっており、DOM level 3 CoreなどはWarking Draftです。
DOMはXMLを解析するに当たって、最初にXML文書の要素構造に対応する木構造をメモリ上に構築します。この概念はJavaScriptでHTMLを操作したことがある方には馴染み深いものでしょう。メモリを大きく消費する一方で、繰り返し更新を掛けてXML文書の構造を変更するような処理に適しているといえます。
DOMツリーは、Javaではインタフェースの集まりになります。ツリーのノードになりうるインタフェースを表1に挙げます。
インタフェース名 | 概要 |
---|---|
Node | DOMツリーの一つのノードを表す。以下は全てNodeインタフェースの継承。 |
Attr | 属性を表す |
CharacterData | 文字データを表す |
Text | CharacterDataを継承し、要素や属性の内容の文字列を表す |
Comment | CharacterDataを継承し、コメントの内容の文字列を表す |
CDATASection | Textを継承し、CDATAセクションを表す |
Document | XML文書全体を表す |
DocumentFragment | DOMツリーの一部を表す |
DocumentType | DTDを表す |
Element | 要素を表す |
Entity | 実体を表す |
EntityReference | 実体参照を表す |
Notation | ノーテーションを表す |
ProcessingInstruction | 処理命令を表す |
表にすると膨大で取り留めなく感じますが、一項目ずつ目を通すと、XML 文書に対する最小限度の種類のノードが用意されていることが分かります。散漫に感じる場合は、ノードの種類に対応するXML整形式規則(XML仕様)を確認していただく必要があります。
DOMの登場当初('97-'98頃)は、JavaScriptの標準化を目的としているように受け取られたこともあります。この当時、JavaScriptは、NetscapeのJavaScript, MicroSoftのJScriptなどのベンダー実装が乖離し始めていました。ECMA Internationalによる標準化の試みであるECMAScriptによっても、なかなか実装される機能が集約されないという、混乱の予兆に満ちていました。
そのため、W3Cは、スクリプト言語の実装に拘泥することを避けて、ドキュメントのモデリングに注力し、Dynamic HTMLの流れを後押ししようと決定したのです。こうして登場したのが、DOMです。DOMは、XML文書というマークアップ言語のモデリングを規定します。
DOMは、誕生の契機こそJavaScriptでしたが、既に普及していたツーリー構造によるモデリングというアイデアを踏襲することを除いては、言語に依存しない、タグによるマークアップ言語において汎用的な、モデリング規則を定める仕様になっています。
Javaは、DOMの主要なターゲット言語の一つという位置づけになります。JavaScriptは、クライアント・サイドでの文書の動的な処理であったり、サーバ・サイドでの負荷分散を目的とした前処理であったりという位置づけになります。一方で、DOMが言語の実装から抽象化されたことで、サーバ・サイドでの主たる処理に利用可能となり、汎用的なプログラミング言語における、強力な要素技術に昇華したのです。Javaは、DOMの可能性を最大限に引き出す能力を持つ言語の一つです。
論より証拠、百聞は一見に如かずです。DOMを使ってXML文書を解析するJavaのソースコードを紹介しましょう。
DOMパーサは、クラスorg.apache.xerces.parsers.DOMParser
で参照します。リスト2は、メソッドparse()
の引数のストリームからXML文書を受け取って、メソッドgetDocument()
でDocument
インスタンスを生成するものです。
リスト1. DomParseDemo.java
import org.w3c.dom.*; import org.apache.xerces.parsers.*; class DomParseDemo { public static void main(String[] args) { try { // DOMパーサの生成 DOMParser parser = new DOMParser(); // XML文書の取得 parser.parse(args[0]); // Documentインスタンスの取得 Document doc = parser.getDocument(); } catch (Exception e) { e.printStackTrace(); } } }
リスト1に誤ったXML文書を読み込ませて見ましょう。「XML文書」で紹介したリスト1のXMLの冒頭をリスト2のように変更しましょう。リスト2は、name
要素の終了タグが存在しないために、整形式ではありません。
リスト2. 誤ったXML(demo2.xml
)
<?xml version="1.0" encoding="UTF-8"?>
<address>
<item sex="male" custid="E21099">
<name>菅井 学
<access kind="email"></access>
...
リスト2のdemo2.xml
を読み込ませる実行結果は、リスト3のようになります。解析途中で例外org.xml.sax.SAXParseException
が発生していることが分かります。
リスト3. 整形式でないXML文書(リスト2)の解析結果
>java DomParseDemo demo2.xml [Fatal Error] demo2.xml:8:7: The element type "name" must be terminated by the matching end-tag "</name>". org.xml.sax.SAXParseException: The element type "name" must be terminated by the matching end-tag "</name>". at org.apache.xerces.parsers.DOMParser.parse(Unknown Source) at DomParseDemo.main(DomParseDemo.java:11) >
一方、正しいXML文書である、リスト1のdemo.xml
を読み込ませた場合は、なにも出力されません。実際には、Documentインスタンスを取得した後、それを使って何らかの作業をすることになります。
リスト4. 整形式のXML文書(リスト1)解析結果
>java DomParseDemo demo.xml >
リスト1ではパーサのエラーを例外でキャッチしようとしましたが、XMLパーサのエラーはイベントとして発生するので、エラー・ハンドラという仕組みで取り扱うことができます。
エラー・ハンドラは、インタフェースorg.xml.sax.ErrorHandler
を実装することで、アプリケーション側で用意します。定義されているメソッドは表2の三つです。
戻り値型 メソッド名(引数リスト) | 概要 |
---|---|
void error(SAXParseException exception) | 解析継続可能なエラー。妥当性制約違反。 |
void fatalError(SAXParseException exception) | 解析継続不能な深刻なエラー。整形式制約違反。 |
void warning(SAXParseException exception) | 警告。 |
インタフェースErrorHandler
の実装クラスとして、org.xml.sax.helpers.DefaultHandler
を使うことも可能です。クラスDefaultHandler
は、インタフェースErrorHandler
のほかに、ContentHandler
, DTDHandler
, EntityResolver
のメソッドも実装しており、このクラスを継承することで、不要なメソッドの実装を省略することができます。
リスト5はリスト1にErrorHandler
を追加したものです。
リスト5. ErrorHandler
の実装例(DomParseDemo2.java
)
import org.w3c.dom.*; import org.apache.xerces.parsers.*; import org.xml.sax.*; class DomParseDemo2 { public static void main(String[] args) { try { // DOMパーサの生成 DOMParser parser = new DOMParser(); // エラー・ハンドラの登録 parser.setErrorHandler(new MyHandler()); // XML文書の取得 parser.parse(args[0]); // Documentインスタンスの取得 Document doc = parser.getDocument(); } catch (Exception e) { e.printStackTrace(); } } } class MyHandler implements ErrorHandler { public void warning(SAXParseException e) { System.out.println("警告: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } public void error(SAXParseException e) { System.out.println("エラー: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } public void fatalError(SAXParseException e) { System.out.println("深刻なエラー: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } }
リスト5のDomParseDemo2
を使ってリスト2(demo2.xml
)を処理したのが、リスト6になります。
リスト6. エラー・ハンドラのメッセージ
>java DomParseDemo2 demo2.xml 深刻なエラー: 8 The element type "name" must be terminated by the matching end-tag "</name>". org.xml.sax.SAXParseException: The element type "name" must be terminated by the matching end-tag "</name>". at org.apache.xerces.parsers.DOMParser.parse(Unknown Source) at DomParseDemo2.main(DomParseDemo2.java:13) >
DTDを持つXML文書に対する妥当性検証を行ってみましょう。リスト2のDTD address.dtdとdemo.xmlを同じディレクトリに配置し、demo.xmlの冒頭にDTD宣言を追加します。また、ここではaddress要素にlang属性を追加してみましょう。
リスト7. 妥当でないXML文書(demo3.xml
)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE address SYSTEM "address.dtd"> <address lang="ja"> ...
リスト7のdemo3.xml
は、DTDに定義されていないlang属性が記述されているため、妥当性検証には引っかかるはずですが、リスト1のDomParseDemoで処理してもなにも出力しません。XercesのDOMパーサは、デフォルトでは妥当性を検証しない設定になっています。そのため、DTD宣言が存在するXML文書でも、DTDがSYSTEM識別子から取得できない、XMLが整形式でない、DTDの構文が間違っているなどの場合を除いて例外を発生しません。
XercesでDOMパーサに妥当性検証を行わせるには、フィーチャーと呼ばれる仕組みで指定します。XercesのDOMパーサのフィーチャーはURIの形式をしていて、非常にたくさんのものが定義されています。ここでは全てを紹介することはできませんが、DOM/SAX共通のもののなかから重要なものを表4に挙げました。詳細についてはXercesのドキュメントで参照いただけます。
フィーチャー | trueの場合の意味 | 省略時値 |
---|---|---|
http://xml.org/sax/features/namespaces | 名前空間を認識する | true |
http://xml.org/sax/features/validation | 妥当性検証を行う | false |
http://apache.org/xml/features/validation/dynamic | DTD宣言が存在するときに限り妥当性検証を行う | false |
http://apache.org/xml/features/validation/schema | XML Schemaによる妥当性検証を行う | false |
http://apache.org/xml/features/continue-after-fatal-error | fatalError後に解析を続ける | false |
http://apache.org/xml/features/dom/include-ignorable-whitespace | 無視できる空白をDOMツリーに含める | false |
妥当性検証を行うためには、parserに対して、次のように記述します。
parser.setFeature("http://xml.org/sax/features/validation", true);
リスト1にフィーチャーの指定を追加したのが、リスト8になります。
リスト8. DomParseDemo3.java
import org.w3c.dom.*; import org.apache.xerces.parsers.*; import org.xml.sax.*; class DomParseDemo3 { public static void main(String[] args) { try { // DOMパーサの生成 DOMParser parser = new DOMParser(); // エラー・ハンドラの登録 parser.setErrorHandler(new MyHandler()); // フィーチャーの指定 parser.setFeature("http://xml.org/sax/features/validation", true); // XML文書の取得 parser.parse(args[0]); // Documentインスタンスの取得 Document doc = parser.getDocument(); } catch (Exception e) { e.printStackTrace(); } } } class MyHandler implements ErrorHandler { public void warning(SAXParseException e) { System.out.println("警告: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } public void error(SAXParseException e) { System.out.println("エラー: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } public void fatalError(SAXParseException e) { System.out.println("深刻なエラー: " + e.getLineNumber() +"行目"); System.out.println(e.getMessage()); } }
リスト8のDomParseDemo3.javaで、改めてdemo2.xml
, demo3.xml
を解析した結果がリスト9になります。
リスト9. 妥当性検証結果
>javac DomParseDemo3.java >java DomParseDemo3 demo2.xml エラー: 2行目 Document is invalid: no grammar found. エラー: 2行目 Document root element "address", must match DOCTYPE root "null". 深刻なエラー: 8行目 The element type "name" must be terminated by the matching end-tag "</name>". org.xml.sax.SAXParseException: The element type "name" must be terminated by the matching end-tag "</name>". at org.apache.xerces.parsers.DOMParser.parse(Unknown Source) at DomParseDemo3.main(DomParseDemo3.java:15) >java DomParseDemo3 demo3.xml エラー: 3行目 Attribute "lang" must be declared for element type "address". >
demo3.xml
の場合は、確かに、DTDに定義されていないlang属性が不正であるため、エラーが報告されています。lang属性を削除して改めて実行していただければ、何も出力されないことを確かめられるでしょう。
demo2.xml
の場合は、DTD宣言が存在しないためのエラーが報告されています。フィーチャーとして、DTD宣言が存在するときに限り妥当性検証を行う、"http://apache.org/xml/features/validation/dynamic
"をtrue
にすれば、このエラーは消えます。また、"http://xml.org/sax/features/namespaces"と"http://apache.org/xml/features/validation/schema
"をtrue
にセットする行を追加すれば、名前空間とXML Schemaを認識するようになるので、「妥当性制約と名前空間」で紹介した、リスト4, 5, 6を組み合わせたXML Schemaを使った文書も解析できるようになります。
// フィーチャーの指定 parser.setFeature("http://xml.org/sax/features/validation", true); parser.setFeature("http://xml.org/sax/features/namespaces", true); parser.setFeature("http://apache.org/xml/features/validation/schema", true);