EdelSoft
     Home     |     Softwares     |     Documents     |     Links     
Home > Documents > Xerces C++ > Starting Up Xerces C++
注意: このページの情報は古くなっています。調べものの参考にはなる可能性がありますが、別途最新の情報を収集してください。
Abstract
Xerces C++ は、W3C の公式勧告である XML 1.0 に準拠した XML 処理環境を提供しています。また、同じく公式勧告である技術やデファクト スタンダードである技術を含め、XML 関連のいくつもの標準をサポートしています。
このページでは、Xerces C++ を使ったプログラミングへのイントロダクションとして、XML 読み取りの主要なプログラミング インタフェースの 1 つである SAX の初歩を説明しながら、小さなサンプルプログラムを作成します。
About SAX
当初 David Megginson 氏の主導によって誕生した SAX すなわち Simple API for XML は、もともと Java において XML をストリーミング処理するために開発されたプログラミング インタフェースです。
SAX では、パーサは XML を先頭から順に解析していき、クライアントコードにとって興味のある対象に出会ったときにイベントを発生させる、つまりクライアントがあらかじめ登録しておいた特定の関数をコールバックします。クライアントコード(つまりわれわれの書くコード)として必要なのは、このようなコールバック用の関数に自分の望む処理を書いておき、それをパーサに登録しておくことです。そして、必要なときにパーサがその関数をコールバックしてくれるのを待ちます。
関数のコールバックというアイデアは昔からの伝統あるプログラミング技法ですが、SAX で採用されている方法はより「オブジェクト指向」的です。Microsoft の COM 技術などにも見られるやり方ですが、コールバックされる関数群はひとまとまりのインタフェースとして定義されています。クライアントコードでは、このインタフェースを実装するクラスを書き、各メソッドにそれぞれ必要な記述をした上で、このクラスのオブジェクトをハンドラとしてパーサに登録することになります。
SAX を使ったプログラムの動きは、おおよそ次のようになります。
  1. XML パーサ オブジェクトを作成する。
  2. カスタマイズされた SAX ハンドラオブジェクトを作成する。
  3. パーサにハンドラを登録する。
  4. パーサに XML の解析を開始させる。
  5. 解析中に適宜、パーサがハンドラのメソッドをコールバックすることでイベントを通知する。
    ...
    ...
    ...
  6. XML データの終了とともに解析が終わる。
DocumentHandler Interface
SAX による XML の解析を体感してみるため、簡単な SAX アプリケーションを書いてみることにします。いま、与えられた XML の中から要素の名称(タグの名前)だけを抽出したいという要請があるとしましょう。
SAX 1.0 において、XML 要素の出現をイベントとして捉えるのは、DocumentHandler インタフェースの startElement メソッドです。Xerces C++ においては、これは抽象クラス DocumentHandler の startElement 純粋仮想関数になります。
virtual void startElement(const XMLCh *const name,
    AttributeList &attrs) = 0;
自分のカスタムクラスに DocumentHandler クラスを継承し、startElement メソッドをオーバライドすれば、望むプログラムが得られます。ただし DocumentHandler クラスには、各種のイベントを捉えるための純粋仮想関数が startElement の他にもたくさんあります。純粋仮想関数を 1 つ以上含むクラスはインスタンス化できないという C++ の制約により、目下のところ必要ないとしても、これらの純粋仮想関数すべてに実装を与えなければなりません。
この場合、すべての純粋仮想関数を「何もしない」関数として定義してしまい、自分が興味のあるメソッドのみに意味のある定義を与えるというのが普通でしょう。実は、SAX はまさにそのために HandlerBase クラスを用意しています。
HandlerBase クラスは、DocumentHandler インタフェースのほか、ErrorHandlerDTDHandlerEntityResolver の各インタフェースを実装し、それらにデフォルトの定義を与えています。DocumentHandler のかわりに HandlerBase クラスを継承するようにすれば、本当に必要なメソッドのみをオーバライドすることに集中できます。
Customized Handler Class
それでは、XML 要素が始まったというイベントに応答し、タグの名前だけを取り出すためのハンドラを書いてみましょう。タグ名の出力は、標準出力に対して行うことにします。
//SampleHandler.h
#ifndef _SAMPLEHANDLER_H_
#define _SAMPLEHANDLER_H_

#include <xercesc/sax/HandlerBase.hpp>

struct SampleHandler : public xercesc::HandlerBase
{
    void startElement(const XMLCh *const name,
        xercesc::AttributeList &attrs);
};

#endif
ハンドラのヘッダはこのようになります。HandlerBase クラスを継承している点と、startElement をオーバライドしている点に注目してください。コンストラクタ、デストラクタ、コピー代入演算子とコピーコンストラクタは、コンパイラがデフォルトで書いてくれるもので間に合います。また、すべてのメンバを public としてよいため、struct でクラスを定義しています。
一方、ハンドラの実装は次のようになります。
//SampleHandler.cpp
#include <xercesc/util/XMLString.hpp>
#include <iostream>
#include "SampleHandler.h"

using xercesc::XMLString;

void SampleHandler::startElement(const XMLCh *const name,
    xercesc::AttributeList &attrs)
{
    char *tagName = XMLString::transcode(name);
    std::cout << tagName << ' ';
    XMLString::release(&tagName);
}
XML の解析中に要素に出会うと、パーサは引数 name に要素名(タグの名称)を、attr に属性リストを示す AttributeList 型オブジェクトへの参照をセットして、登録されているハンドラの startElement メソッドをコールバックします。
よって、startElement 内で name を出力すれば、このプログラムの目的は達せられます。ただし、std::cout << name; のように簡単に出力することはできません。name は const XMLCh 型への const ポインタです。その内容を文字列として標準出力に送るには、char * に変換してやる必要があります(XMCh は Unicode 文字を保持するために Xerces C++ で定義される型で、たいていのコンパイラでは unsigned short の typedef になります)。
XMLCh 型を使った Xerces C++ の内部文字列を null ターミネートされた char 配列に変換するには、XMLString クラスの静的メンバである transcode 関数を使います。この関数は、引数として渡した XMLCh * 文字列を char 文字配列に変換し、その先頭アドレスを返してくれます。ここでは、そのアドレスをローカル変数 tagName で受け取って、std::cout に出力しています。
ここで、std::cout << XMLString::transcode(name); のように、transcode 関数の戻り値をそのまま出力することはできないことに注意してください。このようにすると、メモリリークが発生します。transcode 関数が返すのが、動的に確保されたメモリのアドレスだからです。このメモリを使い終わった後で解放するのはクライアントコード側の責任なので、transcode 関数の戻り値は面倒でも一度変数で受け取って使うことになります。
使い終わったメモリを解放するには、同じく XMLString クラスの静的メンバである release 関数を使います。release 関数にはポインタのアドレスを渡すことに注意してください。
Main Source
ハンドラの定義を終えたので、続いてこのハンドラを実際に使うメインプログラムに移ります。
//Sample.cpp
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/parsers/SAXParser.hpp>
#include <iostream>
#include "SampleHandler.h"

using xercesc::XMLPlatformUtils;
using xercesc::SAXParser;

int main(int argc, const char *argv[])
{
    if(argc < 2){
        std::cout << "\nUsage: Sample <xmlfile>\n\n";
        return -1;
    }

    XMLPlatformUtils::Initialize();

    SAXParser *parser = new SAXParser();
    SampleHandler *handler = new SampleHandler();
    parser->setDocumentHandler(handler);

    parser->parse(argv[1]);

    delete handler;
    delete parser;

    return 0;
}
解析対象となる XML ファイルは、コマンドライン引数としてプログラムの起動時に指定することにしました。引数の数が足りないときは、使い方の説明を表示して終了します。
続いて、XMLPlatformUtils クラスの静的メンバ Initialize 関数を呼んでいます。これは、Xerces C++ を使うプログラムでは必ずクライアントコードの先頭に必要です(さもないと、アプリケーションが愉快でないエラーを起こします)。
Xerces C++ プラットフォームの初期化後、SAXParser クラスのインスタンスを作成します(このインスタンスの作り方は、オリジナルの Java 版 SAX と少し違うところです。興味のある方は調べてみてください)。そして、先ほど書いたカスタムハンドラのインスタンスを作成し、setDocumentHandler メソッドでパーサに登録します。
そして、SAXParser オブジェクトの parse 関数を呼び、XMLの解析を開始します。あとは順次(今回のプログラムでは XML 要素が出現する度に)、パーサがハンドラを呼び出してくれることになります。
最後に、new により構築したオブジェクト parser と handler を忘れずに delete し、プログラムを終了します(このサンプルコードでは SAXParser クラスと SampleHandler クラスのオブジェクトをヒープに構築しました。しかし、自動変数としてスタックに置くこともできます。その場合、もちろん delete は不要です)。
すべてのコードが書けたら、Sample.cpp と SampleHandler.cpp をビルドします。適当な XML ファイルを書いて、プログラムのはたらきを試してみてください。
<?xml version="1.0" encoding="utf-8" ?>
<samples>
  <sample>
    <title>First Appliction</title>
    <chapter>Starting Up Xerces C++</chapter>
    <api>SAX</api>
    <api-version>1.0</api-version>
    <language>C++</language>
    <library>Xerces C++</library>
  </sample>
</samples>
上記の XML ファイルをこのサンプルプログラムに渡すと、以下のようにタグ名が標準出力に送られてきます。
samples sample title chapter api api-version language library
Don't you remember?
サンプルプログラムとはそうしたものですが、このページのプログラムでは、SAX プログラミングの初歩を簡潔に示すため、余分なもの(しかし、本来のプログラムからは省いてはならないもの)を多く取り除きました。
実際には、XML でないファイルが渡されることもあります。また、XML を渡したつもりでも、実は整形式の XML になっていないという場合もあります。XMLPlatformUtils::Initialize 関数や SAXParse::parse 関数は例外を投げることがあるので、例外への備えも忘れてはいけません(それ以前の問題として、new 演算子すら例外を投げることがあります)。また、XML ファイルにひらがなや漢字が含まれている場合はどうでしょうか?
今回のサンプルプログラムは、こういった諸問題への備えをかなり省いているということに注意してください。
さらにもう一つ。今回紹介したのは SAX 1.0 に基くプログラミングですが、SAX は、名前空間をサポートする SAX 2.0 へとバージョンアップしています。Java の API としては、すでに SAX 1.0 は「非推奨」のラベルを貼られてしまっています。C++ の世界ではそういうことはありませんが、今後は SAX 2.0 を積極的に使ったほうがよいでしょう。今回のサンプルプログラムを、SAX 2.0 へと移植してみてください(簡単です)。
Resources
Home > Documents > Xerces C++ > Starting Up Xerces C++