はじめに

JavaでDOM処理を行う場合、

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(xmlFile));
Element elem = (Element)doc.getElementsByTagName(tagName).item(0);

のように記述します。SDKドキュメントを見るとDocumentBuilderFactoryとDocumentBuilderは抽象クラス、DocumentやElementはインターフェースであることがわかります。では実際に処理を行っているのは誰なのでしょうか?今回はここら辺のからくりを読み解きたいと思います。

DocumentBuilderFactory

まずDocumentBuilderFactoryについて見ていきましょう。 newInstance()の動作はSDKドキュメントに記述されています。newInstance()ではDocumentBuilderFactoryを実装するクラスをいろいろな方法で探しているようです。JavaではこのようにAPIだけ定義しておいて実際の処理を行うクラスはプロパティで切り替えられるようにしておくという方式がよく用いられています。

では最後のデフォルトのDocumentBuilderFactoryインスタンスって何者でしょうか?ソースを見ると

public static DocumentBuilderFactory newInstance() {
  return (DocumentBuilderFactory) FactoryFinder.find(
    /* The default property name according to the JAXP spec */
    "javax.xml.parsers.DocumentBuilderFactory",
    /* The fallback implementation class name */
    "org.apache.crimson.jaxp.DocumentBuilderFactoryImpl");
}

となっています。つまりcrimsonがデフォルトのDocumentBuilderFactoryとして利用されていることがわかります。それではcrimsonがどのようにDOMを実装しているか見ていきましょう。

DocumentBuilderImpl

ソースを見るとnewDocumentBuilder()の実装は単純に新しいDocumentBuilderImplを作っているだけなことがわかります。次にDocumentBuilderImplのコンストラクタを見てみるとxmlReader(XMLReaderImplオブジェクト)のセットアップ、builder(XmlDocumentBuilderオブジェクト)のセットアップが行われていることがわかります。xmlReaderの各種ハンドラにbuilderを設定しているところからxmlReaderとbuilderが協調してDOMが構築されているのだろうと想像できます。実際にparse()のソースを見てみると、

xmlReader.parse(is);
return builder.getDocument();

となっており、XMLの読み込みが終わった時点でDOMが構築されていることがわかります。

Parser2, XmlDocumentBuilder

XMLReaderImpl.parse()を見てみると実際の処理はParser2が行っていることがわかります。で、Parser2を見てみると3000行以上あって読む気がなくなってくるのですが何をやっているかというとXML仕様に基づいてXML文書をparsingしているだけです。今回の目的であるDOMの構築で重要なのは真ん中ぐらいにあるmaybeElement()の

contentHandler.startElement(parts[0], parts[1], parts[2], attTmp);

です。これによりXmlDocumentBuilderにElementの情報が渡されます。

ではElement情報を受け取ったXmlDocumentBuilderが何をしているかというと以下のような処理をしています。

e = (ElementNode) document.createElementEx(qName);
elementStack[topOfStack++].appendChild(e);
elementStack[topOfStack] = e;

つまりElementが見つかるたびにElementをスタックに積んでいます。また、endElement()が呼ばれると一番上の要素が下ろされます。スタックを下ろされた時点でそのElementの構築は完了しています。なお、elementStackの一番下(topOfStack = 0)にはXmlDocumentオブジェクトが置かれています。

コードだけだとイメージがつかみにくいので簡単なXMLで動作をトレースしてみましょう。

<root>
  <foo>
    <bar>baz</bar>
  </foo>
  <hoge>huga</hoge>
</root>

こんな感じなXMLがあったとすると、

  1. elementStack[top] => document
  2. document.appendChild(root)
  3. elementStack[top] => root
  4. root.appendChild(foo)
  5. elementStack[top] => foo
  6. foo.appendChild(bar)
  7. elementStack[top] => bar
  8. elementStack[top] => foo
  9. elementStack[top] => root
  10. root.appendChild(hoge)
  11. elementStack[top] => hoge
  12. elementStack[top] => root
  13. elementStack[top] => document

とスタックが成長していきます。


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS