Xerces/Xalan-C++ で XML

DOMで単純なプログラミング

最終更新: 2015-03-31 (火) 22:05:09 (1359d)

概要

このページで説明するコード

DOM とは?

DOM とは、SAX と並ぶ XML から得られた情報を受け取るもう一つの方法だ。 XML で書かれた文章とは、タグが入れ子になったものである。 従って、XML文章を単純な木構造として考えることができる。 DOM(Document Object Model)とは、実際にXML文章を木構造に直したもののことを言う。

DOM の XML パーサは、XML 文章を木構造のデータに変換してくれる。 木構造を構成するノードは全て DOMNode の派生クラスであり、 タグに相当する DOMElement クラス、タグ内の文字列に相当する DOMText クラスなどがある。 DOM を用いたプログラミングでは、DOM の XML パーサから得られた木構造を操作することが中心となる。

タグと、テキストを出力するプログラムを作る

作るもの

これから fileexample.xml という XML ファイルを読み込み、 以下の処理を行う簡単なプログラムを製作する。

  • 開始・終了タグの名前を出力する。
  • タグ内のテキストを出力する。

大まかな流れとしては、 まず最初に、XML パーサから情報を受け取り処理するクラスとして SampleWriter クラスを製作する。 次に、main 関数で fileexample.xml を実際に読み込むようにする。

SampleWriterクラスを作る

DOM では、XML パーサから得られた木構造を受け取り、それを処理するためのクラスを一つ用意する。 今回は SampleWriter クラスを用意した。 SampleWriter クラスの定義として、fileSampleWriter.hpp の一部を以下に示す。

#include <xercesc/dom/DOMNode.hpp>
#include <xercesc/dom/DOMElement.hpp>
#include <xercesc/dom/DOMText.hpp>

class SampleWriter {
public:
  void write(xercesc::DOMNode* node);

private:
  void writeElement(xercesc::DOMElement* element);
  void writeText(xercesc::DOMText* text);
};

SampleWriter クラスは、XML パーサから得られた木構造である DOMNode を受け取るメソッドを定義する。 また、タグを処理する writeElement メソッドとタグ名のテキストを処理する writeText メソッドを定義する。 DOMElement クラスも DOMText クラスも DOMNode クラスの派生クラスである。 他にも DOMNode の派生クラスがいくつかの存在し、必要に応じて定義する必要がある。 (調べるためには このページを参照すると良い。 同じページが Xerces-C++ に付属のドキュメントにも存在する。)

次に SampleWriter クラスの具体的なメソッドの中身を見てみよう。 write メソッドの定義として、fileSampleWriter.cpp の一部を以下に示す。

void SampleWriter::write(DOMNode* node) {
  if(node) {
    // ノードの種類によって処理を配分する
    switch(node->getNodeType()) {
    case DOMNode::ELEMENT_NODE:
      // タグだったら
      writeElement(static_cast<DOMElement*>(node));
      break;
    case DOMNode::TEXT_NODE:
      // テキストだったら
      writeText(static_cast<DOMText*>(node));
      break;
    }  // switch

    // 深さ方向に書き出していく
    DOMNode* child=node->getFirstChild();
    while(child) {
      DOMNode* next=child->getNextSibling();
      write(child);
      child=next;
    }  // for child
  }  // if
}

write メソッドは、大きく分けてノード種類による振り分け処理と木構造の巡回処理の二つの処理からなる。

ノード種類による振り分け処理がなぜ必要なのか? それは DOMNode は全てのノードのルートクラスであるが、 そのままではノード特有の情報を取得することができないからである。 実際にノードから情報を取得するときにはノードの種類ごとにキャストを行ってから使用する。 DOMNode の getNodeType メソッドはノードの種類を返すので、 switch 文で種類ごとに処理を振り分ける必要がある。

木構造の巡回処理だが、DOMNode は木構造のある特定のノードを表しており、一般的に子ノードを持っている。 XML 文章の全てにアクセスするということは、子ノードも適切に処理するようにプログラムを書かなければならない。 DOMNode の getFirstChild メソッドはノードが持っている最初の子ノードを返し、 getNextSibling メソッドはノードの次の兄弟ノードを返す。 深さ方向に再帰的に処理を進めていくことで全てのノードを処理することができる。

次に writeElement メソッドの定義として、fileSampleWriter.cpp の一部を以下に示す。

void SampleWriter::writeElement(DOMElement* element) {
  char* name=XMLString::transcode(element->getTagName());
  cout << "tag    : " << name << endl;
  XMLString::release(&name);
}

DOMElement クラスはタグに相当するノードで、 getTagName メソッドでタグの名前を取得できる。 そこで、XMLString::transcode メソッドで取得したタグ名を char 型に変換し出力する。 transcode メソッドで生成した char 型のデータは使い終わったら release メソッドで開放するのを忘れないようにしよう。

main関数を作る

さて SampleWriter クラスを作り終えたら、 main 関数で実際に XML パーサに XML ファイルを読み込ませるプログラムを書こう。 filemain.cpp の一部を以下に示す。

int main() {
  // Xerces-C++を初期化する
  try {
    XMLPlatformUtils::Initialize();
  } catch(...) {
    cerr << "Xerces-C++の初期化に失敗しました。" << endl;
    return 1;
  }

  static const XMLCh gLS[]={chLatin_L, chLatin_S, chNull};
  DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(gLS);
  DOMLSParser* parser=((DOMImplementationLS*)impl)->createLSParser(
      DOMImplementationLS::MODE_SYNCHRONOUS, 0);

  try {
    DOMDocument* document=parser->parseURI("example.xml");
    SampleWriter writer;
    writer.write(document);
  } catch(...) {
    cerr << "ファイルの解析に失敗しました。" << endl;
  }

  parser->release();

  XMLPlatformUtils::Terminate();

  return 0;
}

まず Xerces-C++ は、使う前に XMLPlatformUtils::Initialize で初期化しなければならない。 さらに使い終わったら XMLPlatformUtils::Terminate で終了しなければならないことを覚えよう。

Xerces-C++ における DOM のための XML パーサは、DOMBuilder クラスである。 それを生成したら parseURI メソッドで、fileexample.xml を読み込ませ、 XML 文章から得られた DOMDocument(これも DOMNode の派生クラスである)を SampleWriter に渡せばいい。 最後に生成した XML パーサを release メソッドで破棄するのを忘れないように。

Xerces-C++ 3.0.0 以降とそれより前の違い

Xerces-C++ 3.0.0 で DOM のインターフェイスは大きく変更されている。 そのため Xerces-C++ 3.0.0 より前の DOM を使って書いたソースコードは基本的にそのままでは利用できないと考えた方が良い。

とは言え、全く使えないわけでもない。 詳細な調査はしていないため、楽観的には言えないが恐らくは名前の変更が主なものと考えられる。

本サンプルでの影響箇所は、以下の通りである。

Xerces-C++ 3.0.0 より前Xerces-C++ 3.0.0 以降
DOMBuilderDOMLSParser
DOMImplementationLS.createDOMBuilderDOMImplementationLS.createLSParser

日本語への対応とより簡潔な記述

本ページで紹介した SampleWriter::writeElement メソッドは、XML ドキュメントに日本語があると正常に動作しない。

以下のページで日本語を扱う仕組みについて説明している。

上記のページを参考にしても良いし、 本コンテンツ内で技術的解説を加えた上で公開しているクラスをオープンソースのユーティリティーとして sourceforge.jp で公開しているのでそちらを利用することもできる。

Xerces-C++ Utils(sourceforge.jp) を利用すると以下のように簡潔に記述できる。

void SampleWriter::writeElement(DOMElement* element) {
  xercesc_utils::XMLCh2CharTranscoder toTranscoder("Shift_JIS");
  cout << "tag    : " << toTranscoder::transcode(element->getTagName()) << endl;
}

コメント

最新の10件を表示しています。 コメントページを参照

  • 最新のバージョン(3.1.1)では既にコンパイルが通らない状態になっていますね。見直します。 -- トゥイー 2011-05-03 (火) 12:37:39
  • 調査したところ Xerces-C++ 3.0.0 から DOMBuilder クラスが無くなったみたいですね…。とりあえず今わかったのはそこまでです。引き続き、なぜなくなったのか、Version 3.0.0 以前と以後を統一的に扱う方法等について調査します。 -- トゥイー 2011-05-04 (水) 10:16:00
  • 参考にさせていただきたいので是非更新をお願いします。 -- ななし 2011-05-30 (月) 19:08:56
  • 今、まさに調査中です。DOMBuilder が無くなって、DOMLSParser というのを代わりに使うらしいということがわかりました。ちなみにブログに書いていますが DOMLSParser の LS は、Load and Save の略だと思われます。DOM Level 3 の仕様のことですね。 -- トゥイー 2011-05-30 (月) 20:02:16
  • Xerces-C++ の Subversion を調査したところリビジョン番号 224543(2005年7月24日) に DOMBuilder から DOMLSParser に名前が変更されています。その更新でのコメントはまさに「DOM Level 3 Load and Save: updated interfaces」でした。 -- トゥイー 2011-05-30 (月) 20:15:48
  • 変更の分岐点は、Xerces-C++ 3.0.0 です。バージョンの違いを吸収する方法については、別途ページを作成しようと思っています。 -- トゥイー 2011-05-30 (月) 20:38:43
  • みなさんにお願い。表に記載のもの以外に大きく影響を受けている部分があれば、ここにコメントを残してもらえれば何らかの形でまとめます。 -- トゥイー 2011-05-30 (月) 20:40:31
お名前: