DOM - Document Object Model

revised: Oct./26th/03

DOMの概要

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に挙げます。

表1. DOMツリーのノードの種類
インタフェース名概要
NodeDOMツリーの一つのノードを表す。以下は全てNodeインタフェースの継承。
Attr属性を表す
CharacterData文字データを表す
TextCharacterDataを継承し、要素や属性の内容の文字列を表す
CommentCharacterDataを継承し、コメントの内容の文字列を表す
CDATASectionTextを継承し、CDATAセクションを表す
DocumentXML文書全体を表す
DocumentFragmentDOMツリーの一部を表す
DocumentTypeDTDを表す
Element要素を表す
Entity実体を表す
EntityReference実体参照を表す
Notationノーテーションを表す
ProcessingInstruction処理命令を表す

表にすると膨大で取り留めなく感じますが、一項目ずつ目を通すと、XML 文書に対する最小限度の種類のノードが用意されていることが分かります。散漫に感じる場合は、ノードの種類に対応するXML整形式規則(XML仕様)を確認していただく必要があります。

DOMに関する若干の歴史

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による解析

論より証拠、百聞は一見に如かずです。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の三つです。

表2. ErrorHandlerのメソッド
戻り値型 メソッド名(引数リスト)概要
void error(SAXParseException exception)解析継続可能なエラー。妥当性制約違反。
void fatalError(SAXParseException exception)解析継続不能な深刻なエラー。整形式制約違反。
void warning(SAXParseException exception)警告。

インタフェースErrorHandlerの実装クラスとして、org.xml.sax.helpers.DefaultHandlerを使うことも可能です。クラスDefaultHandlerは、インタフェースErrorHandlerのほかに、ContentHandler, DTDHandler, EntityResolverのメソッドも実装しており、このクラスを継承することで、不要なメソッドの実装を省略することができます。

リスト5はリスト1ErrorHandlerを追加したものです。

リスト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());
    }
}

リスト5DomParseDemo2を使ってリスト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)
>

DOMによる妥当性検証

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のドキュメントで参照いただけます。

表3. Xercesの代表的なフィーチャー
フィーチャーtrueの場合の意味省略時値
http://xml.org/sax/features/namespaces名前空間を認識するtrue
http://xml.org/sax/features/validation妥当性検証を行うfalse
http://apache.org/xml/features/validation/dynamicDTD宣言が存在するときに限り妥当性検証を行うfalse
http://apache.org/xml/features/validation/schemaXML Schemaによる妥当性検証を行うfalse
http://apache.org/xml/features/continue-after-fatal-errorfatalError後に解析を続ける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);


Copyright © 2003 SUGAI, Manabu. All Rights Reserved.