Xerces-C++を最大限に利用する-パート2

DOMの実装

この2部構成の記事は、Xerces-C++XMLライブラリについて入門者向けに書かれています。このパート2でRick Parrishは、Document Object Model (DOM) ドキュメントのロード、操作あるいは合成する方法、それにパート1で作成したのと同じ棒グラフをScalable Vector Graphics (SVG) を使用して、作成する方法をお見せします。これらの記事を読んだC++プログラマは、XMLの構文解析や独自のアプリケーションにXMLの処理を簡単に追加できるようになるでしょう。

Rick Parrish (rfmobile@swbell.net), Independent consultant

Rick Parrish氏は、高校生の頃からコンピューター・サイエンスに興味を持ち始めましたが、エレクトロニクスにはもっと早くから関心を持っていました。最初は電気工学の教育を受けていましたが、ソフトウェアの場合は、ハードウェアとは違って、塩化第二鉄溶液の嫌な臭いをかぐ必要はなく、ただ設計変更のためだけに指にやけどをする危険を冒す必要もないことに気付きました。Rickは、長い間C/C++ のプログラミングに携わってきましたが、VBとDelphi(Pascal) による仕事も多く、アセンブリー言語の仕事も少なくありません。今でも、熱いハンダを必要とする1つか2つのプロジェクトに何とか携わっています。Windows 2000は素晴らしいけれど、Ogg Vorbisもクールだし、IPTableのあるLinux 2.4カーネルは本当にタフだ! というのが彼の現在の持論です。現在の関心事は、ソフトウェア・モデリング設計用のツールを開発するオープン・ソース・プロジェクトを始めることです。Rickの連絡先は、 rfmobile@swbell.net です。



2003年 8月 15日

パート1ではLinuxやWindows用に書かれたアプリケーションにライブラリをリンクする方法、およびSAX APIで構文解析する方法を見てきました。また、サンプル・アプリケーションでは、ASCIIアートで棒グラフを作成する方法をお見せしました。

今回は、DOMノードの構成について紹介します。そして、ファイルやストリームからDOMドキュメントを生成するためのロードと構文解析、プログラム的にDOMドキュメントを生成するための合成、永続化やファイル、ストリームに出力としてDOMドキュメントを書き出す方法を説明します。

DOMの処理にバージョン2.0以前のXerces-C++を使ったことがある場合には、注意が必要です。色々と変更されています・・・。一番大きな変更は大部分のDOMクラス・オブジェクトの名前がDOM_プレフィックスからDOM プレフィックスに変わったことと、参照ではなくポインタを渡すように変わったことでしょう。

DOMノード型

DOMの基本的なデータ型はDOMNodeクラスです。すべてのDOMノード・オブジェクトはこの基本型の拡張です。表1にDOMのノード型を示します。最初の列はDOMNode::getNodeType()メソッドが返す型名を列挙したものです。2列目はその特定のノード型のインスタンスを宣言するのに使用されるC++のクラスです。3列目は実際にそうしたインスタンスを生成する場合に使用される、構築メソッドを表します。

表1. DOMのノード型
DOM TypeDOM ClassConstruction method
DOMNode::TEXT_NODEDOMTextcreateTextNode
DOMNode::PROCESSING_INSTRUCTION_NODEDOMProcessingInstructioncreateProcessingInstruction
DOMNode::DOCUMENT_NODEDOMDocumentcreateDocument
DOMNode::ELEMENT_NODEDOMElementcreateElement
DOMNode::ATTRIBUTEDOMAttrcreateAttribute
DOMNode::ENTITY_REFERENCE_NODEDOMEntityReferencecreateEntityReference
DOMNode::CDATA_SECTION_NODEDOMCDATASectioncreateCDATASection
DOMNode::COMMENT_NODEDOMCommentcreateComment
DOMNode::DOCUMENT_TYPE_NODEDOMDocumentTypecreateDocumentType
DOMNode::ENTITY_NODEDOMEntitycreateEntity
DOMNode::NOTATION_NODEDOMNotationcreateNotation

XML_DECL_NODE ノード型が、Xerces-C++ version 2では、DOMDocumentのメソッド(get/setEncodingget/setVersion それにget/setStandalone)に置き換えられていることに注意してください。

表1の構築メソッドはどれもDOMDocumentクラスから利用します。DOMDocumentオブジェクトを生成するためには、DOMImplementationのインスタンスから構築する方法しかなく、これにより「鶏が先か卵が先か」の問題を回避しています。DOMImplementationのインスタンスはDOMImplementation::getImplementation()メソッドを呼ぶことで取得できます。DOMImplementation::getImplementation()はスタティック・メソッドとして宣言されています。ドキュメント・ノードは、次のように2段階で生成することになります。

DOMImplementation *pImpl = DOMImplementation::getImplementation();
DOMDocumentType* pDoctype = pImpl->createDocumentType(L"svg", 
     NULL, L"svg-20000303-stylable.dtd");
pSVG = pImpl->createDocument(L"svg", L"svg", pDoctype);

DOMのロードと構文解析

リスト8のコードはパーサを初期化し、XML文書をDOMツリーとしてロードします。ここでは、ロードからDOMツリーの作成までをDOMParserオブジェクトを使って行います。パーサの処理が正常に終了した後、getDocument() をコールすることで生成されたDOMツリーを取得することができます。

パーサは、3つの例外(DOM、SAX、XML)のうちどれか1つをスローします。そこで私は、リスト8に例外ハンドラーをスタブとして入れておきました。エラーをチェックする別の方法として、DOMParser::getErrorCount機能があります。


合成

リスト9のコード・サンプルはDOM APIのコールを使ってXML文書を構築(別の言い方をすれば「合成」)します。構築された文書は、たまたま小さなXHTMLページですが、実際にはどのようなXMLでも構築することができます。XML文書が実際に生成されたことを証明するために、ここに挙げたリストではXML文書、タグやその他すべてをコンソールにダンプします(ドキュメントをコンソールに表示するのに必要なコードは、後ほどお見せします)。

リスト9のコードを見ていると、他のノードを作るのにDOMドキュメント・オブジェクトがどう使われているかが分かると思います。また、各ノードがどのようにしてその親ノードに明示的に付加されているかも分かると思います。ルート・ノードですらappendChildメソッドを使ってドキュメントに明示的に付加する必要があります。


永続化

DOM APIを実装するライブラリであれば、DOMドキュメントをファイルまたはストリームとして保持するような機能が、そのライブラリに組込まれていると期待するでしょう。そうした機能は基底クラス、XMLFormatTargetXMLWriterにて記述されています。それらの実装は、クラス名XMLWriterLocalFileFormatTargetStdOutFormatTargetの中に隠蔽されています。

リスト10のコードは、ファイルまたは、標準出力ストリームに書き出すためにXMLFormatterオブジェクトを生成します。XMLFormatterオブジェクトは、文字セット間の変換やXMLで予約された文字を含む可能性のあるテキストを拡張するなどの処理を行います。XMLWriterはDOMツリーをトラバースし、XMLデータの一連の塊を取り出し、フォーマッタに渡して出力できるようにします。DOMの内容をチェックするために、そのときだけのつもりでdump_xmlを使った場合、要素間に改行が入らないと言う欠点があります。最終的なデータとして必ずしも必要なわけではありませんが、ちょっと余計なホワイトスペースがあるとデバッグの際にXMLがずっと読みやすくなります。次のリストは、DOMツリーにテキストノードを追加することなしに、読み易さに十分なホワイトスペースを追加するようにする、リスト10に対する2行の追加です。

リスト11.  XMLの読み易い出力
  // turn on serializer "pretty print" option

  if ( pSerializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true) )
  	pSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

リスト10と11のコードは、デバッグのときに便利に使えるので取っておいてください。XMLドキュメントを作る必要が無い場合でも、動作しているDOMのサブツリーのスナップショットがとれるのは便利だと分かるはずです。この記事のサンプルコードをダウンロードしてみれば(参考文献)、UFF-16でデータをファイルに書き出すワイド文字列対応版があることが分かるでしょう。以前のXerces-C++のバージョンでは、XMLFormatterオブジェクトを生成する際に、「UTF-16(LE)」エンコーディングでは成功するのに「UTF-16」では失敗してしまうのに私は驚いたのですが、バージョン2でこの問題は修正されています。


DOMのトラバース

リスト11DOMPrintコードは、DOMツリーの全てのノードを巡回する方法を示しています。同じことをするのにイテレータを使った例をリスト12で紹介し、引き続きツリー・ウォーカを使った例も紹介します。リスト12に示すノード・イテレータのコードでは、有効なDOMノード変数rootが既に存在していることを前提にしています。

リスト12. 全てのテキストノードを巡回するイテレータを作成する
// create an iterator to visit all text nodes.
DOMNodeIterator iterator = 
  doc.createNodeIterator(root, DOMNodeFilter::SHOW_TEXT, NULL, true);
// use the tree walker to print out the text nodes.
for ( current = iterator.nextNode(); current != 0; current = iterator.nextNode() )
  // note: this leaks memory!
  std::cout << current.getNodeValue().transcode();
std::cout << std::endl;

リスト12の例は単に全ドキュメントをザーッと動き回ってテキストノードを拾い出し、表示するものです。coutのワイド文字列対応版、wcoutに注意してください。リスト13はツリー・ウォーカのコードですが、これも有効なDOMノード変数rootが既に存在していることを前提にしています。

リスト13. 全てのテキストノードを巡回するウォーカを作成する
// create a walker to visit all text nodes.
DOMTreeWalker walker = 
  doc.createTreeWalker(root, DOMNodeFilter::SHOW_TEXT, NULL, true);
// use the tree walker to print out the text nodes.
for (DOMNode current = walker.nextNode(); current != 0; current = walker.nextNode() )
  // note: this leaks memory!
  std::cout << current.getNodeValue().transcode();
std::cout << std::endl;

リスト13のツリー・ウォーカの例では、ツリー・ウォーカに追加されている機能を何も使ってはいないので、ノード・イテレータとまったく同じように機能します。createTreeWalkerを使って最初にツリー・ウォーカを作ったとき、getCurrentNode()メソッドはフィルタや何を検索するかの設定に関わらずrootノードを返します。nextNode()への最初のコールがあって初めてgetCurrentNode()が期待通りに動くようになります。


DOMの操作

DOM APIのおかげで、樹医にでもなったようにDOMツリーのノードを枝打ちしたり、接木したり、剪定したりできるようになります。DOMツリーを操作するメソッドと、まったく初めからDOMツリーを合成するのに使うメソッドは、一部重複しています。リスト14はそうしたメソッドをまとめたものです。

リスト14. DOMNodeメソッドのまとめ
DOMNode cloneNode(bool deep) const;
DOMNode insertBefore(const DOMNode &newChild, const DOMNode &refChild);
DOMNode replaceChild(const DOMNode &newChild, const DOMNode &oldChild);
DOMNode removeChild(const DOMNode &oldChild);
DOMNode appendChild(const DOMNode &newChild);
DOMNode insertBefore(const DOMNode &newChild, const DOMNode &refChild);
DOMNode replaceChild(const DOMNode &newChild, const DOMNode &oldChild);

ノード固有の生成メソッドであるcreateTextNodeメソッドやcreateElementメソッドはDOMDocumentオブジェクトからしか使えませんが、cloneNodeメソッドはどのようなDOMNodeオブジェクトからでも使用できます。

DOMElementノードには他にも属性を追加したり、削除したりするのに使えるメソッドがいくつかあります。これをリスト15に示します。

リスト15.DOMElementメソッドのまとめ
void setAttribute(const DOMString &name, const DOMString &value);
DOMAttr setAttributeNode(DOMAttr newAttr);
void setAttributeNS(const DOMString &namespaceURI, const DOMString &qualifiedName, 
     const DOMString &value);
DOMAttr removeAttributeNode(DOMAttr oldAttr);
void removeAttribute(const DOMString &name);
void removeAttributeNS(const DOMString &namespaceURI, const DOMString &localName);

不注意でDTDに定義されている要素から属性を削除すると、時に予期せぬ結果をもたらすことになります。例えば、DTDで属性のデフォルト値を定義していた場合、その要素の元のXMLがどうであったかにかかわらず、そのデフォルト値を持った属性がDOMツリー上に出現してしまうことになります。DOM APIを使ってDOMツリーからその属性を削除してしまうと、その属性はデフォルト値で置き換えられるのです。つまり、デフォルト値が設定してある属性ノードは削除できないと言うことです。

樹医ごっこはこのくらいにして、これらを使って何か実際に有用なことをしてみましょう。SAXベースのグラフ表示アプリケーションも悪くはありませんが、上司の受けは今一でした・・・。もうDOM APIを自由に使えるのですから、XMLの構文解析はもちろん、生成することもできるわけです。前回の棒グラフをもっとカッコ良くしてみましょう。DOMを使って、AdobeのプラグインSVGビューア、W3CブラウザAmaya、またはSVG対応のMozilla(1.0とそれ以降)などで表示するのに適したScalable Vector Graphics (SVG) バージョンを作成してみましょう。

前回と同じXMLデータを入力として使いますが、その出力はリスト16のような形になります。

リスト16.  XML/SVG出力の例
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<!DOCTYPE svg SYSTEM "svg-20000303-stylable.dtd">
<svg width="320" height="200">
<g style="font-size:14">
  <rect x="100" y="20" style="stroke:node; fill:red" width="20" height="20"/>
  <text x="20" y="35">North</text>
</g>
<g style="font-size:14">
  <rect x="100" y="40" style="stroke:node; fill:blue" width="100" height="20"/>
  <text x="20" y="55">South</text>
</g>
<g style="font-size:14">
  <rect x="100" y="60" style="stroke:node; fill:yellow" width="27" height="20"/>
  <text x="20" y="75">East</text>
</g>
<g style="font-size:14">
  <rect x="100" y="80" style="stroke:node; fill:violet" width="23" height="20"/>
  <text x="20" y="95">West</text>
</g>
<g style="font-size:14">
  <rect x="100" y="100" style="stroke:node; fill:orange" width="75" height="20"/>
  <text x="20" y="115">Central</text>
</g>
<text x="20" y="135" style="font-size:10">Reported figures are preliminary only.</text>
</svg>

SVG互換のXMLタグを出力するようにすれば、これを実装するのにSAXを使うこともできます。SVG出力にある、「!DOCTYPE」宣言に注意してください。文書型は、SVGの技術的な勧告のどのバージョンが期待されているのかを知る上で、ビューアにとって重要な手がかりになります。ちょっと工夫すれば、Xerces-C++でドキュメント出力にDOCTYPEを含むようにできます。リスト16のSVGがビューアでどのように見えるか図2に示します。

図2. SVG出力のスクリーン・ショット
図2. SVG出力のスクリーン・ショット

次のリスト17は、XMLの元データを読み込んでSVG用の最終結果を出力するDOMコードです。

出力のSVGドキュメントに「!DOCTYPE」宣言を含めるようにする工夫は、ドキュメントを作成するのにDOMDocument::createDocument() の代わりにDOMImplementation::createDocument()を使うことです。これに関するコードは、dom2svg静的関数の初めの方にあります。DOMImplementationを使って生成すれば、DOMDocumentTypeノードを含めて生成することができるようになります。DOMDocumentを使用して生成する場合には、文書型を指定することができません。

DOMDocument::createDocumentType()を使ってDOMDocumentTypeを生成することはできますが・・・。この方法では、DOCTYPEにシステムIDやパブリックIDを設定できないので、ここでは余り役に立ちません。もう一つ、小さなことですが、このテクニックを使えば、トップレベルのルート要素のノードも作成してくれるのです。明示的にルート要素を生成してドキュメント・オブジェクトに付加すること無しに、このコードがgetDocumentElement()をコールできるのはそのためです。


結論

今回の記事と前回の記事(Part 1)で、Xerces-C++ライブラリの利点として、オープン・ソースであること、ポータビリティ(可搬性)、寛容なライセンスやコミュニティのサポートなどがあることを知りました。ソース・コードを調べることで、このライブラリの動作を分析することができます。また、C++コンパイラをサポートするプラットフォームなら、どんなプラットフォームにも実装できます。WindowプログラマにはC++、Visual Basic、それにVBScript/JScriptからでも使えるCOMバージョンがあります。Apacheライセンスでは、ユーザーに対する簡単な法的開示と断り書きで、Xerces-C++を商用に使用することができます。メーリングリストで他のプログラマと、開発に関する質問やそれに対する答えを共有することもできます。こうした利点から、あなたのプロジェクトにXML機能を追加する上でXerces-C++は最適の選択と言えるでしょう。


ダウンロード

内容ファイル名サイズ
x-xercc/xml4c.zipx-xercc/xml4c.zip---

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=243021
ArticleTitle=Xerces-C++を最大限に利用する-パート2
publish-date=08152003