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ツリーを取得することができます。

リスト8: SAXパーサの実装例(ハンドラ無し)
// GraphMain.cpp
#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/ContentHandler.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>
#include <xercesc/sax2/Attributes.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/framework/LocalFileInputSource.hpp>
#include <stdio.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include "GraphHandler.h"
#include "XercesString.h"
#ifdef XERCES_CPP_NAMESPACE_USE
XERCES_CPP_NAMESPACE_USE
#endif
int main(int argc, char* argv[])
{
  if (argc < 2) return -1;
  // initialize the XML library
  XMLPlatformUtils::Initialize();
  if ( !access(argv[1], 04) )
  {
    printf("Cheesey Bar Graph!\n");
	XercesString wstrPath(argv[1]);
    SAX2XMLReader* pParser = XMLReaderFactory::createXMLReader();
    LocalFileInputSource source(wstrPath);
	// our application specific handler.
    GraphHandler handler;
    pParser->setFeature( XercesString("http://xml.org/sax/features/validation"), true );
    pParser->setFeature( XercesString("http://apache.org/xml/features/validation/dynamic"), true );
    pParser->setContentHandler(&handler);
    pParser->setErrorHandler(&handler);
    pParser->parse(source);
  }
  // terminate the XML library
  XMLPlatformUtils::Terminate();
  getchar();
  return 0;
}

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


合成

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

リスト9: SAXイベント・ハンドラ宣言の例

リスティングを見るにはここをクリック

リスト9: SAXイベント・ハンドラ宣言の例

//GraphHandler.hpp
#include <xercesc/sax2/DefaultHandler.hpp>
#include <vector>
#include <algorithm>
#include "XercesString.h"
struct department
{
  XercesString mName;
  double mSales;
  double mInventory;
  double mLabor;
  department() : mSales(0.0), mInventory(0.0), mLabor(0.0) { };
  department(const department &copy) : mName(copy.mName)
  {
    mSales = copy.mSales;
    mLabor = copy.mLabor;
    mInventory = copy.mInventory;
  };
 department(const XMLCh *wstr) : mName(wstr), mSales(0.0), mInventory(0.0), mLabor(0.0) { };
  virtual ~department() { };
};
class GraphHandler : public DefaultHandler
{
  XercesString mName;
  std::vector<department> mList;
public:
  virtual void startDocument();
  virtual void endDocument();
    virtual void startElement(
    const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const Attributes& attrs);
    virtual void endElement(
    const XMLCh* const uri, const XMLCh* const localname,
    const XMLCh* const qname);
  virtual void characters(
    const XMLCh* const chars,
    const unsigned int length);
};

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


永続化

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

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

リスト10: SAXイベント・ハンドラの実装例
// GraphHandler.cpp
#include "GraphHandler.h"
#include <xercesc/sax2/Attributes.hpp>
#include <stdio.h>
#include "XercesString.h"
#include <float.h>
static const bool bDebug = false;
void GraphHandler::startDocument()
{
  if (bDebug) printf("starting...\n");
}
// given a value, minimum, maximum and overall discrete number of tiles;
// computes how many tiles or blocks will be displaced by the value.
static int tiles(double fValue, double fMin, double fMax, int nSpan)
{
  double fDel = fMax - fMin;
  fValue -= fMin;
  if (fDel <= FLT_MIN) fValue = 0.;
  else fValue /= (fMax - fMin);
  return (int)(fValue * nSpan);
}
// returns a null terminated string whose length 
// is the requested span (up to some limit).
static const char *bars(int nSpan)
{
  static const char strBars[] = "########################################"
	"########################################";
  const int kLimit = sizeof strBars - 1;
  if (nSpan > kLimit) nSpan = kLimit;
  return &strBars[kLimit - nSpan];
}
void GraphHandler::endDocument()
{
  int nDex = 0;
  double fMin = 0.0, fMax = 0.0;
  if (bDebug) printf("...ends.\n");
  const unsigned kLimit = mList.size();
  if (bDebug)
  {
    printf("Department               Sales Inventory     Labor\n");
    printf("-------------------- --------- --------- ---------\n");
  }
  for (nDex = 0; nDex < kLimit; nDex++)
  {
    double fValue = mList[nDex].mSales;
    if (!nDex)
      fMin = fMax = fValue;
    else
    {
      if (fValue > fMax) fMax = fValue;
      else if (fValue < fMin) fMin = fValue;
    }
    if (bDebug)
    {
      char *strName = XMLString::transcode(mList[nDex].mName);
      printf("%-20.20s %9.2f %9.2f %9.2f\n", strName, mList[nDex].mSales, 
	    mList[nDex].mInventory, mList[nDex].mLabor);
	  XMLString::release(&strName);
    }
  }
  const int nSpan = 70;
  fMin *= 0.8;
  for (nDex = 0; nDex < kLimit; nDex++)
  {
    int nWidth = tiles(mList[nDex].mSales, fMin, fMax, nSpan);
    char *strName = XMLString::transcode(mList[nDex].mName);
    printf("%-20.20s (%7.0f) %s\n", strName, mList[nDex].mSales, bars(nWidth) );
	XMLString::release(&strName);
  }
}
void GraphHandler::startElement(
		const XMLCh* const uri, 
		const XMLCh* const localname, 
		const XMLCh* const qname, 
		const Attributes& attrs)
{
  if (bDebug) printf("element...[%ws, %ws, %ws]\n", uri, localname, qname);
  const XercesString kstrFigures("figures");
  const XercesString kstrType("type");
  const XercesString kstrValue("value");
  const XercesString kstrDepartment("department");
  const XercesString kstrSales("sales");
  const XercesString kstrLabel("label");
  const XercesString kstrLabor("labor");
  const XercesString kstrInventory("inventory");
  const XercesString kstrName("name");
  const XercesString kstrCorporate("corporate");
  if ( !XMLString::compareString(localname, kstrFigures) )
  {
    const XercesString wstrType( attrs.getValue(kstrType) );
    const XercesString wstrValue( attrs.getValue(kstrValue) );
	double fValue = wcstod(wstrValue, NULL);
    if (mList.size())
    {
      if ( !XMLString::compareString(wstrType, kstrSales) )
        mList[mList.size() - 1].mSales = fValue;
      else if ( !XMLString::compareString(wstrType, kstrInventory) )
        mList[mList.size() - 1].mInventory = fValue;
      else if ( !XMLString::compareString(wstrType, kstrLabor) )
        mList[mList.size() - 1].mLabor = fValue;
    }
  }
  else if ( !XMLString::compareString(localname, kstrDepartment) )
  {
    const XercesString wstrValue( attrs.getValue(kstrName) );
    mList.insert(mList.end(), department(wstrValue) );
  }
  else if ( !XMLString::compareString(localname, kstrCorporate) )
  {
    mName = XMLString::replicate( attrs.getValue(kstrName) );
  }
}
void GraphHandler::endElement(
  const XMLCh* const uri, const XMLCh* const localname,
  const XMLCh* const qname)
{
  if (bDebug) printf("...end element.\n");
}
void GraphHandler::characters(
  const XMLCh* const chars,
  const unsigned int length)
{
  if (!bDebug) return;
  char *strChars = new char [length+1];
  XMLString::transcode(chars, strChars, length);
  strChars[length] = 0;
  printf("chars [ %-0.*s ] \n", length, strChars);
  delete[] strChars;
}
リスト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コードです。

リスト17. XMLからSVGを生成する

リスティングを見るにはここをクリック

リスト17. XMLからSVGを生成する

// svg.cpp
#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMImplementation.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/dom/impl/DOMWriterImpl.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLString.hpp>
#include <iostream>
#include <xercesc/sax/SAXException.hpp>
#include <xercesc/dom/DOMWriter.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/framework/LocalFileInputSource.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <stdio.h>
#include <stdlib.h>
#include "XercesString.h"

static XMLCh *value(XMLCh wszBuf[], unsigned uVal)
{
  char szLocal[32] = {0};
  sprintf(szLocal, "%u", uVal);
  XMLString::transcode(szLocal, wszBuf, 32);
  return wszBuf;
}

static bool getvalue(DOMElement &department, const XMLCh *strAttr, long *pnValue)
{
  bool bFound = false;
  DOMNode *pCursor = department.getFirstChild();

  while (pCursor != NULL && !bFound)
  {
    // is this a <department> element?
    if ( pCursor->getNodeType() == DOMNode::ELEMENT_NODE &&
		!XMLString::compareString(pCursor->getNodeName(), XercesString("figures") )  )
    {
      DOMElement *pFigure = (DOMElement*)pCursor;
      const XMLCh *strType = pFigure->getAttribute( XercesString("type") );
      bFound = !XMLString::compareString(strType, strAttr);
      if (bFound)
      {
        const XMLCh *strValue = pFigure->getAttribute( XercesString("value") );
		char *strTmp = XMLString::transcode(strValue);
		*pnValue = strtol(strTmp, NULL, 0);
      }
    }
    pCursor = pCursor->getNextSibling();
  }
  return bFound;
}

static bool getrange(DOMDocument &doc, const XMLCh *strAttr, long *pnMin, long *pnMax)
{
  long nMin = 0L, nMax = 0L;
  bool bOnce = false;
  DOMElement* pCorporate = doc.getDocumentElement();
  DOMNode* pCursor = pCorporate->getFirstChild();
  while (pCursor != NULL)
  {
    // is this a <department> element?
    if ( pCursor->getNodeType() == DOMNode::ELEMENT_NODE &&
      !XMLString::compareString(pCursor->getNodeName(), XercesString("department") ) )
    {
      long nValue = 0L;
      DOMElement* pDepartment = (DOMElement *)pCursor;
      if ( getvalue(*pDepartment, strAttr, &nValue) )
      {
        if (bOnce)
        {
          if (nMin > nValue) nMin = nValue;
          if (nMax < nValue) nMax = nValue;
        }
        else nMin = nMax = nValue;
        bOnce = true;
      }
    }
    pCursor = pCursor->getNextSibling();
  }
  *pnMax = nMax;
  *pnMin = nMin;
  return bOnce;
}

// transforms input corporate XML data to SVG viewable XML output data.
static bool dom2svg(DOMDocument &doc, const XMLCh *strAttr, DOMDocument **ppSVG)
{
  static const char *strColors[] =
    {"red", "blue", "yellow", "violet", "orange", "green"};
  const unsigned kLimit = sizeof strColors / sizeof strColors[0];
  unsigned uColor = 0; // use as a color index
  long nMin = 0L, nMax = 0L, nRange = 0L;
  DOMDocument *pSVG = NULL;

  if ( !getrange(doc, strAttr, &nMin, &nMax) )
    return false;

  nRange = nMax - nMin;
  // these next three lines of code create our output object,
  // set it's doctype, and create the root document element.
  DOMImplementation *pImpl = DOMImplementation::getImplementation();
  DOMDocumentType* pDoctype = pImpl->createDocumentType( XercesString("svg"), NULL, XercesString("svg-20000303-stylable.dtd") );
  pSVG = pImpl->createDocument(XercesString("svg"), XercesString("svg"), pDoctype);
  if (pSVG != NULL)
  {
    // output related nodes are prefixed with "svg" 
    // to distinguish them from input nodes.
	pSVG->setEncoding( XercesString("UTF-8") );
    DOMElement *pSvgRoot = pSVG->getDocumentElement();
    // sneak in the XML declaration just ahead of the doctype.
    //pSVG->insertBefore(svgDecl, pSVG->getDoctype() );
    // locate the XML root <corporate> element
    DOMElement* pCorporate = doc.getDocumentElement();
    // did we locate the root element?
    if (pCorporate != NULL)
    {
      XMLCh wszBuf[32] = {0};
      unsigned uX = 20, uY = 20;
      // yes: walk through the list of department elements beneath it.
      DOMNode* pCursor = pCorporate->getFirstChild();
      while (pCursor != NULL)
      {
        // is this a <department> element?
        if (  pCursor->getNodeType() == DOMNode::ELEMENT_NODE &&
          !XMLString::compareString(pCursor->getNodeName(), XercesString("department") )  )
        {
          XercesString wstrStyle("stroke:node; fill:");
          DOMElement* pDepartment = (DOMElement *)pCursor;
          long nValue = 0L;
          if ( getvalue(*pDepartment, strAttr, &nValue) )
          {
            // yes: create an SVG branch for this department
            DOMElement* pSvgG = pSVG->createElement( XercesString("g") );
            pSvgRoot->setAttribute( XercesString("width"), value(wszBuf, 320) );
            pSvgRoot->setAttribute( XercesString("height"), value(wszBuf, 200) );
            pSvgRoot->appendChild(pSvgG);
            // this <g> element will hold any SVG attributes (like font size 
            // and color) to be shared among the elements for this department.
            DOMElement* pSvgRect = pSVG->createElement( XercesString("rect") );
            pSvgG->appendChild(pSvgRect);
            pSvgG->setAttribute( XercesString("style"), XercesString("font-size:14") );
            // fill in the <rect> attributes
            wstrStyle.append( XercesString(strColors[uColor++ % kLimit]) );
            pSvgRect->setAttribute( XercesString("style"), wstrStyle );
            pSvgRect->setAttribute( XercesString("x"), value(wszBuf, uX+80) );
            pSvgRect->setAttribute( XercesString("y"), value(wszBuf, uY) );
            if (!nRange) nValue = 0;
            else nValue = 80 * (nValue - nMin) / nRange;
            nValue += 20;
            pSvgRect->setAttribute( XercesString("width"), value(wszBuf, nValue) );
            pSvgRect->setAttribute( XercesString("height"), value(wszBuf, 20) );
            DOMElement* pSvgText = pSVG->createElement( XercesString("text") );
            pSvgG->appendChild(pSvgText);
            DOMText* pSvgName = pSVG->createTextNode( pDepartment->getAttribute( XercesString("name") ) );
            pSvgText->setAttribute( XercesString("x"), value(wszBuf, uX) );
            pSvgText->setAttribute( XercesString("y"), value(wszBuf, uY+15) );
            pSvgText->appendChild(pSvgName);
            uY += 20;
          }
        }
        pCursor = pCursor->getNextSibling();
      }
      DOMElement* pSvgText = pSVG->createElement( XercesString("text") );
      pSvgText->setAttribute( XercesString("x"), value(wszBuf, uX) );
      pSvgText->setAttribute( XercesString("y"), value(wszBuf, uY+15) );
      DOMText* pSvgDisclaimer = pSVG->createTextNode( XercesString("Reported figures are preliminary only.") );
      pSvgText->appendChild(pSvgDisclaimer);
      pSvgText->setAttribute( XercesString("style"), XercesString("font-size:10") );
      pSvgRoot->appendChild(pSvgText);
    }
  }
  *ppSVG = pSVG;
  return true;
}

// Tries to return an XML DOM document 
// object for a given file name.
static bool doc2dom(const XMLCh *src, DOMDocument** ppDoc)
{
  bool bSuccess = false;
  //DOMString strMessage;

  XercesDOMParser *parser = new XercesDOMParser;
  if (parser)
  {
	char *strSrc = XMLString::transcode(src);
    parser->setValidationScheme(XercesDOMParser::Val_Auto);
    parser->setDoNamespaces(false);
    parser->setDoSchema(false);
    parser->setCreateEntityReferenceNodes(false);
    //parser->setToCreateXMLDeclTypeNode(true);
    try
    {
	  LocalFileInputSource source(src);
      bSuccess = false;
      parser->parse(source);
      bSuccess = parser->getErrorCount() == 0;
      if (!bSuccess)
      {
        std::cerr << "Parsing " << strSrc;
        std::cerr << " error count: " << parser->getErrorCount() << std::endl;
      }
    }
    catch (const DOMException& e)
    {
      std::cerr << "DOM Exception parsing ";
      std::cerr << strSrc;
      std::cerr << " reports: ";
	  // was message provided?
	  if (e.msg)
	  {
		// yes: display it as ascii.
		char *strMsg = XMLString::transcode(e.msg);
        std::cerr << strMsg << std::endl;
		XMLString::release(&strMsg);
	  }
	  else
	    // no: just display the error code.
        std::cerr << e.code << std::endl;
    }
    catch (const XMLException& e)
    {
      std::cerr << "XML Exception parsing ";
      std::cerr << strSrc;
      std::cerr << " reports: ";
      std::cerr << e.getMessage() << std::endl;
    }
    catch (const SAXException& e)
    {
      std::cerr << "SAX Exception parsing ";
      std::cerr << strSrc;
      std::cerr << " reports: ";
      std::cerr << e.getMessage() << std::endl;
    }
    catch (...)
    {
      std::cerr << "An exception parsing ";
      std::cerr << strSrc << std::endl;
    }
    // did the input document parse okay?
    if (bSuccess)
      *ppDoc = parser->getDocument();

	XMLString::release(&strSrc);
  }
  return bSuccess;
}

// appends the suggested file extension to the 
// file path if it does not already have one.
static void massage(XercesString &wstrPath, const XMLCh *strTail, bool bChop = false)
{
  bool bTail = false;
  int nDex = wstrPath.size();
  while (!bTail && nDex--)
  {
    const XMLCh ch = wstrPath[nDex];
    switch (ch)
    {
      case L'.':
        bTail = true;
        break;
	  case L'/':
	  case L'\\':
        break;
      default:
        break;
	}
  }
  if (!bTail)
  {
    wstrPath.append(strTail);
  }
  else if (bChop)
  {
	wstrPath.erase(wstrPath.begin() + nDex, wstrPath.end());
    wstrPath.append(strTail);
  }
}

int main(int argc, char* argv[])
{
  char *src = NULL, *dst = NULL;

  if (argc == 2)
  {
    dst = src = argv[1];
  }
  else if (argc == 3)
  {
    src = argv[1];
    dst = argv[2];
  }

  // initialize library
  XMLPlatformUtils::Initialize();
  if (src && dst)
  {
    DOMDocument* pDoc = NULL;
	XercesString wstrSrc(src), wstrDst(dst);

    // massage parameters into file names with proper extension.
    massage(wstrSrc, XercesString(".xml") );
    massage(wstrDst, XercesString(".svg"), true);
    // attempt to parse the document into a DOM object.
    if ( doc2dom(wstrSrc, &pDoc) )
    {
      DOMDocument* pSvg = NULL;
	  DOMWriterImpl writer;
	  LocalFileFormatTarget fileTarget(wstrDst);
	  //StdOutFormatTarget coutTarget;

      if ( pDoc->hasChildNodes() )
      {
        // parsed okay convert input DOM object
        // into an SVG output DOM object.
        dom2svg(*pDoc, XercesString("sales"), &pSvg);
        // write the resulting document.
		writer.setEncoding( XercesString("UTF-8") );
		writer.writeNode(&fileTarget, *pSvg);
		//writer.writeNode(&coutTarget, *pSvg);
      }
      else 
        std::cerr << "empty document!\n";
    }
    else
      std::cerr << "Opening & parsing document " << src << " fails!";
  }
  else
  {
    // throw the user a clue.
    std::cerr << "Usage:\n";
    std::cerr << "xml2svg <in-file> <out-file>\n";
    std::cerr << " -- or --\n";
    std::cerr << "xml2svg <in-file>\n";
  }
  // cleanup library
  getchar();
  XMLPlatformUtils::Terminate();
  return 0;
}

出力の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