株式会社エス・スリー・フォー

Xercesが変わった!?

はい、Xerces-C 2.x から、かなり大きく変わりました。少なくとも従来のコードを’そのまま’コンパイルできなくなっています。 それ以外にも細かな相異がいくつかあります。

とりあえず昔のままで…

インタフェースが大きく変わってしまったのですが、幸いにも旧いインタフェースは残されています。従来のinclude:

#include <xercesc/dom/DOM.hpp>

は、

#include <xercesc/dom/deprecated/DOM.hpp>

のように .../deprecatedに置かれていますから、#include
の修正だけで’とりあえず’はコンパイルできます。

とはいえ、deprecated には違いないので早めに新たなインタフェースで書き直すことをお薦めします

ICUを使わせるには?

Xerces-CにICUを使わせるには?と全く同じ手順で使えるようになります。すなわち:

ICU
  1. ICU 2.2 を適当なディレクトリに展開し、そのディレクトリ・パスを環境変数 ICU_ROOT に設定しておく。
  2. $(ICU_ROOT)/source/allinone/allinone.dsw で build

    $(ICU_ROOT)/bin に DLL ができるから、PATHを通す。

Xerces-C 2.x
  1. Xerces-C 2.0.0 のソース版を適当なディレクトリに展開する。

  2. (VC++の場合) IDEから Projects/Win32/VC6/xerces-all/xerces-all.dsw を開け、XercesLibをbuild-targetとする。
  3. プロジェクト設定を以下のとおり変更:

    • include-path に $(ICU_ROOT)¥include を追加
    • マクロ XML_USE_WIN32_TRANSCODER 改め XML_USE_ICU_TRANSCODER に
    • library-path に $(ICU_ROOT)¥lib を追加。
    • ライブラリ icuuc.lib (Debug版では icuucd.lib) を追加。
  4. FileView: XercesLib/util/Transcoders にある Win32TransService.(hpp|cpp) を引っこ抜いて、跡地にsrc/xercesc/util/Transcoders/ICU/ICUTransService.(hpp|cpp) を投げ込む。

build!
  1. Build/Win32/VC6/(Release|Debug) に lib/dll ができあがる。

文字コード変換がICUに置き換わったことを確認するには、例えばiso-2022-jp(JIS)で書かれたXMLをパースしてください。

後述する”で、どう変わったのですか?”にあるコードがそれです。

標準C++ライブラリに依存しますか?

はい、依存します。そのために、標準C++ライブラリをたとえばSTLPortなどに取り替えるとなれば、Xerces-C自体も同じ環境で再構築しなければなりません。

Xerces-Cのソースコードを眺めてみたところ、標準C++依存部はたった一箇所でした。

以下に示すのは Xerces-C 2.1.0 の …/src/xercesc/framework/StdOutFormatTarget.cppです:

#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <iostream.h>
#include <stdio.h>

StdOutFormatTarget::StdOutFormatTarget()
{}

StdOutFormatTarget::‾StdOutFormatTarget()
{}

void StdOutFormatTarget::writeChars(const XMLByte* const  toWrite
                                  , const unsigned int    count
                                  , XMLFormatter* const   formatter)   
{
        // Surprisingly, Solaris was the only platform on which
        // required the char* cast to print out the string correctly.
        // Without the cast, it was printing the pointer value in hex.
        // Quite annoying, considering every other platform printed
        // the string with the explicit cast to char* below.
    cout.write((char *) toWrite, (int) count);
}

これを以下のように修正し、再構築してください。標準C++依存部分がなくなります:

#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <stdio.h>

StdOutFormatTarget::StdOutFormatTarget()
{}

StdOutFormatTarget::‾StdOutFormatTarget()
{}

void StdOutFormatTarget::writeChars(const XMLByte* const  toWrite
                                  , const unsigned int    count
                                  , XMLFormatter* const   formatter)   
{
        // Surprisingly, Solaris was the only platform on which
        // required the char* cast to print out the string correctly.
        // Without the cast, it was printing the pointer value in hex.
        // Quite annoying, considering every other platform printed
        // the string with the explicit cast to char* below.
    fwrite(toWrite, 1, count, stdout);
}

v1.xで文字コードの変換が思わしくないのですが…

どうやらそのようです。v1.5〜v1.7あたりでは、UNICODEからnativeへの変換:DOMString::transcode()の挙動に不審な点が見受けられます。

かわりに XMLString::transcodeをお薦めします。

/*
 * DOMString output を native に変換し、プリントする 
 */

  DOMString    output; // UNICODE文字列
  XMLCh wbuffer[32]; // 十分な大きさのバッファ(変換先)
  XMLString::copyNString(wbuffer, output.rawBuffer(), output.length());
  wbuffer[output.length()] = L'¥0';
  char* buffer = XMLString::transcode(wbuffer);
  std::cout << buffer << std::endl;
  delete[] buffer;

v2.xではDOMString自体deprecateされたので、いずれにせよXMLStringを用いたコード変換が主流となるでしょう

で、どう変わったのですか?

従来(v1.x)のDOM_NodeはHandle/Bodyパターンを駆使することによってポインタを排除し、Java-likeなインテフェースを提供していました。しかしこのJava-likeなインタフェースはその実現のためパフォーマンスを落としていました。

v2.xからはこのJava-likeなインタフェースを一掃(deprecate)し、ポインタを’素’で見せる実装に改められました。

加えて従来の文字列 DOMStringもdeprecateされ、UNICODE文字列へのポインタ XMLCh*に改められました。

以下は簡単なXMLドキュメントをパースし、出力するコードです(v1.7.0)

#include <string>
#include <iostream>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/DOMParser.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/util/PlatformUtils.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  {
                      "<country>¥x1b¥x24¥x42¥x46¥x7c¥x4b¥x5c¥x1b¥x28¥x42</country>";

  MemBufInputSource source((const XMLByte*)input.data(), input.size(), "memory_buffer", false);
  
  DOMParser parser;
  parser.parse(source);

  DOM_Document document = parser.getDocument();
  DOM_Element  element  = document.getDocumentElement();
  DOM_Text     text     = static_cast<DOM_Text&>(element.getFirstChild());

  DOMString    output   = text.getData();
  XMLCh wbuffer[32];
  XMLString::copyNString(wbuffer, output.rawBuffer(), output.length());
  wbuffer[output.length()] = L'¥0';
  char* buffer = XMLString::transcode(wbuffer);
  std::cout << buffer << std::endl;
  delete[] buffer;
  }
  XMLPlatformUtils::Terminate();
  return 0;
}

同じものを v2.1.0 の新 API で書き直すと以下のようになります。

#include <string>
#include <iostream>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/util/PlatformUtils.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  {
                      "<country>¥x1b¥x24¥x42¥x46¥x7c¥x4b¥x5c¥x1b¥x28¥x42</country>";

  MemBufInputSource source((const XMLByte*)input.data(), input.size(), "memory_buffer", false);
  
  XercesDOMParser parser;
  parser.parse(source);

  DOMDocument* document = parser.getDocument();
  DOMElement*  element  = document->getDocumentElement();
  DOMText*     text     = static_cast<DOMText*>(element->getFirstChild());

  char         buffer[32];
  XMLString::transcode(text->getData(), buffer, 32);
  std::cout << buffer << std::endl;
  }
  XMLPlatformUtils::Terminate();
  return 0;
}

ポインタはdeleteしなくていいのですか?

新APIで定義されたDOMNodeにはメソッドrelease()が定義されており、これを使ってメモリの解放を行ないます

DOMNode* node;
node->release();

このメソッドはノードの子ノードを再帰的にreleaseします

ただし、パーサからgetDocument()で取り出したドキュメント、すなわちDOMNodeは、パーサのデストラクトと同時にreleaseされます。DOMNodeを温存したままパーサだけを先に廃棄するときは、パーサのメソッドadoptDocumentでドキュメントを取り出してください。

  DOMDocument* document;
  {
    XercesDOMParser parser;
    parser.parse(...);
    document = parser.adoptDocument();
  }
  ...
  document->release();

DOMNodeをファイルに出力するには?

DOMWriter が装備されました。使用例を以下に示します。

#include <iostream>
#include <locale>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMWriter.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/util/XMLUni.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  std::locale::global(std::locale("japanese"));

  DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(L"LS");
  DOMDocument* doc = impl->createDocument(0, L"会社",0);
  DOMElement* rootElem = doc->getDocumentElement();
  DOMElement*  prodElem = doc->createElement(L"製品");
  rootElem->appendChild(prodElem);
  DOMText*    prodDataVal = doc->createTextNode(L"Xerces-C");
  prodElem->appendChild(prodDataVal);
  DOMElement*  catElem = doc->createElement(L"品種");
  rootElem->appendChild(catElem);
  catElem->setAttribute(L"アイデア", L"お見事");
  DOMText*    catDataVal = doc->createTextNode(L"XMLパーサ");
  catElem->appendChild(catDataVal);
  DOMElement*  devByElem = doc->createElement(L"作ったひと");
  rootElem->appendChild(devByElem);
  DOMText*    devByDataVal = doc->createTextNode(L"あぱっち");
  devByElem->appendChild(devByDataVal);

  DOMWriter* writer = ((DOMImplementationLS*)impl)->createDOMWriter();
  writer->setEncoding(L"shift_jis");
  StdOutFormatTarget target;
  writer->writeNode(&target, *doc);
  delete writer;

  doc->release();
  XMLPlatformUtils::Terminate();
  return 0;
}

この例では標準出力に出力しています。

ファイルに出力するには、StdOutFormatTargetLocalFileFormatTargetに置き換えてください。