Xerces/Xalan-C++ で XML

XMLCh 型と char 型の変換をカプセル化しよう(日本語編)

最終更新: 2015-04-01 (水) 20:35:08 (1358d)

概要

日本語を含むXMLファイルを使うために必要となる XMLTranscoder クラスの基本的な使い方は、 Xerces-C++ で日本語を使うまでを事前に参照のこと。

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

注意

unsigned int 型となっている箇所を XMLSize_t に変更した。

これは Xerces-C++ 3.0.0 以降のリリースに影響しての変更である。

Xerces-C++ 2 系の利用者は、XMLSize_t を unsigned int と読み替えること。

XMLSize_t 型について調査した結果は以下のブログ記事にて言及している

fileフルセット(VC++ 6.0/Xerces-C++ 2 系用)では unsigned int を用いているが、それ以外のファイルは XMLSize_t を利用している。

補足

本コンテンツ内で技術的解説を加えた上で公開しているクラスをユーティリティーとしてオープンソースとして sourceforge.jp で公開する。

利用を検討する方は以下のサイトを参考のこと。

XMLTranscoder の生成・破棄を隠蔽するクラスを作る

XMLTranscoder の生成そして破棄

XMLTranscoder を用いた文字列の変換には、 最初に XMLTranscoder を生成し、対象となる文字列を変換し、最後に XMLTranscoder を破棄するという繰り返し記述が現れる。 Xerces-C++ を使ったプログラムでは頻繁に文字列の変換を行う必要があるため、 変換・破棄の繰り返し記述が面倒だ。 また、最初に生成した XMLTranscoder は何度でも再利用ができるため、 文字列の変換が起こるたびに生成・破棄を繰り返す必要は本来無い。 そこで、XMLTranscoder の生成と破棄を XMLCh2CharTranscoder というクラスに隠蔽してしまおう。 XMLCh2CharTranscoder クラスの定義として、 fileXMLCh2CharTranscoder.hpp の一部を以下に示す。

class XMLCh2CharTranscoder {
private:
  /// トランスコーダです。
  xercesc::XMLTranscoder* transcoder_;

  /// transcodeする際に使用するバッファのサイズです。
  XMLSize_t bufferSize_;

  /// transcodeする際に必要となるバッファです。
  XMLByte* buffer_;

public:
  explicit XMLCh2CharTranscoder(const char* const encoding, XMLSize_t bufferSize=1024);
  ~XMLCh2CharTranscoder();

	// ...
};

XMLCh2CharTranscoder クラスは、 指定されたエンコードとバッファサイズによって XMLCh 型から char 型への変換を行うクラスで、 そのために必要となる XMLByte 型のバッファと XMLTranscoder を持っている。 さらにコンストラクタとデストラクタの実装として、 fileXMLCh2CharTranscoder.cpp の一部を以下に示す。

XMLCh2CharTranscoder::XMLCh2CharTranscoder(const char* const encoding, int bufferSize) :
bufferSize_(bufferSize), buffer_(new XMLByte[bufferSize+4]) {
  XMLTransService::Codes failReason;
  transcoder_=XMLPlatformUtils::fgTransService->makeNewTranscoderFor(
    encoding, failReason, bufferSize_, XMLPlatformUtils::fgMemoryManager
    );
}

XMLCh2CharTranscoder::~XMLCh2CharTranscoder() {
  delete buffer_;
  delete transcoder_;
}

XMLCh2CharTranscoder クラスは、 コンストラクタで指定されたサイズのバッファと指定されたエンコードの XMLTranscoder を生成し、 デストラクタでそのバッファと XMLTranscoder を破棄する。

コピーコンストラクタと代入演算子の禁止

また、buffer_・transcoder_ の二重解放が行われないようにするためにコピーコンストラクタと代入演算子を禁止する。

class XMLCh2CharTranscoder {
private:
  // コピーと代入を禁止します。
  explicit XMLCh2CharTranscoder(const XMLCh2CharTranscoder& transcoder);
  XMLCh2CharTranscoder& operator=(const XMLCh2CharTranscoder& transcoder);
};

文字列を管理するクラスを作る

文字列の生成そして破棄

後は、XMLCh2CharTranscoder クラスに実際に文字列を変換する transcode メソッドを定義すれば終わりなのだが、 変換した結果の文字列を管理するクラスとしてCharPtrクラスというものを作ることを考えよう。 Xerces-C++ で日本語を使うまでで紹介したプログラムの transcode メソッドでは、 char 型のポインタとして変換結果を返していたため、以下のように変換した結果を自分で破棄しなければならなかった。

void SampleHandler::startElement(const XMLCh* const uri, const XMLCh* const localname,
                                 const XMLCh* const qname, const Attributes& attrs) {
  char* name=transcode(localname);
  cout << "start  : " << name << endl;
  delete[] name;
}

これを CharPtr クラスに隠蔽する。 CharPtr クラスの定義として、fileCharPtr.hpp の一部を以下に示す。

class CharPtr {
private:
  char* buffer_;

public:
  CharPtr() : buffer_(new char[1]) {
    buffer_[0]=0;
  }

  explicit CharPtr(const char* const buffer) {
    copy(buffer);
  }

  ~CharPtr() {
    delete [] buffer_;
  }

private:
  void copy(const char* const buffer) {
    buffer_=new char[xercesc::XMLString::stringLen(buffer)+1];
    xercesc::XMLString::copyString(buffer_, buffer);
  }

  // ...
};

CharPtr クラスは、コンストラクタで受け取った文字列をコピーし、 デストラクタでその文字列を破棄する。 デフォルトコンストラクタでは、終端文字列(\0)のみの文字列を持たせる。 こうすることで、対象となる文字列の破棄を自動的に行うことができるようになる。

文字列の伸長

さらに CharPtr クラスには、重要な機能として文字列を足し合わせて伸長させる機能を持たせる。 XMLTranscoder が一度に変換できる文字列長が固定であり、 少しずつ変換した結果を足し合わせて全文字列を変換することになるが、 その際に文字列を足し合わせる機能が CharPtr クラスにあると便利だからである。 CharPtrクラスの定義として、fileCharPtr.hpp の一部を以下に示す。

class CharPtr {
  // ...

  CharPtr& operator+=(const char* const ptr) {
    const XMLSize_t size=xercesc::XMLString::stringLen(buffer_)+xercesc::XMLString::stringLen(ptr);
    char* newBuffer=new char[size+1];
    xercesc::XMLString::copyString(newBuffer, buffer_);
    xercesc::XMLString::catString(newBuffer, ptr);
    delete [] buffer_;
    buffer_=newBuffer;

    return *this;
  }

  // ...
};

コピーコンストラクタと代入演算子

XMLTranscoder を使って変換した結果を返す際に、変数のコピーまたは代入が発生する。 従って、適切にコピーまたは代入が行われるようにコピーコンストラクタと代入演算子を定義しなければならない。 CharPtr クラスの定義として、fileCharPtr.hpp の一部を以下に示す。

class CharPtr {
  // ...

public:
  explicit CharPtr(const CharPtr& ptr) {
    if(this!=&ptr) {
      copy(ptr.buffer_);
    }
  }

  CharPtr& operator=(const CharPtr& ptr) {
    if(this!=&ptr) {
      copy(ptr.buffer_);
    }
    return *this;
  }

  // ...
};

これらのメソッドでは、受け取った CharPtr の buffer を自分の buffer にコピーする。

便利なメソッドの追加

このままでは使いづらいので、使い勝手を良くするために二つのメソッドを追加する。 CharPtr クラスの定義として、fileCharPtr.hpp の一部を以下に示す。

class CharPtr {
public:
  // ...

  operator const char* const () const {
    return buffer_;
  }

  friend static std::ostream& operator<<(std::ostream& stream, const CharPtr& buffer) {
    return stream << (const char* const)buffer;
  }
};

一つは char* 型へのキャスト演算子で、もう一つは出力ストリームへの << 演算子だ。 これを定義しておくことで、CharPtr 型を char 型と同じような感覚で使うことが可能になる。

XMLTranscoder を使って文字列を変換する

最初に定義した XMLCh2CharTranscoder クラスに文字列を変換する機能として transcode メソッドを定義する。 このメソッドは、 Xerces-C++ で日本語を使うまでで紹介した transcode メソッドに酷似しているが、 CharPtr クラスを利用している点で異なる。 XMLCh2CharTranscoder クラスの定義として、 fileXMLCh2CharTranscoder.cpp の一部を以下に示す。

CharPtr XMLCh2CharTranscoder::transcode(const XMLCh* toTranscode) const {
  const XMLSize_t count=XMLString::stringLen(toTranscode);
  const XMLCh* srcPtr=toTranscode;
  const XMLCh* endPtr=toTranscode+count;
  XMLSize_t charsEaten;
  CharPtr result;

  while(srcPtr < endPtr) {
    const XMLSize_t srcCount=endPtr-srcPtr;
    const XMLSize_t srcChars=srcCount > bufferSize_ ? bufferSize_ : srcCount;

    const XMLSize_t outBytes=transcoder_->transcodeTo(
      srcPtr, srcChars, buffer_, bufferSize_, charsEaten, XMLTranscoder::UnRep_Throw
      );

    if(outBytes) {
      buffer_[outBytes]=buffer_[outBytes+1]=buffer_[outBytes+2]=buffer_[outBytes+3]=0;
    }

    srcPtr+=charsEaten;
    result+=(char*)buffer_;
  }

  return result;
}

XMLCh2CharTranscoder クラスの transcode メソッドは、変換した結果として CharPtr を返す。 CharPtr は文字列を自動的に破棄してくれるので、使う側は文字列の破棄を考えなくて済む。

XMLCh2CharTranscoder クラスを使う

SAXで単純なプログラミングで紹介したプログラムを XMLCh2CharTranscoder クラスを使って書き直してみよう。 まずは、SampleHandler クラスに XMLCh2CharTranscoder を持たせる。 SampleHandler クラスの定義として、 fileSampleHandler.hpp の一部を以下に示す。

class SampleHandler : public xercesc::DefaultHandler {
private:
  XMLCh2CharTranscoder transcoder_;

  CharPtr transcode(const XMLCh* const buffer) const {
    return transcoder_.transcode(buffer);
  }

public:
  SampleHandler() : transcoder_("Shift_JIS") {
  }

  // ...
};

次に SampleHandler クラスの startElement メソッドの定義として、 fileSampleHandler.cpp の一部を以下に示す。

void SampleHandler::startElement(const XMLCh* const uri, const XMLCh* const localname,
                                 const XMLCh* const qname, const Attributes& attrs) {
  cout << "start  : " << transcode(localname) << endl;
}

char 型から XMLCh 型への変換についても考える

XMLCh 型から char 型への変換をカプセル化するクラスとして XMLCh2CharTranscoder クラスを作ったが、 その逆、char 型から XMLCh 型への変換も同じようにカプセル化する Char2XMLChTranscoder クラスを作ることができる。 XMLCh2CharTranscoder クラスのヘルパークラスとして CharPtr クラスは、char 型の文字列を管理するクラスであったが、 それと同様に Char2XMLChTranscoder クラスのヘルパークラスとして、 XMLCh 型の文字列を管理するクラスとして XMLChPtr クラスを定義する。

また、今回は XMLChPtr クラスに特殊なメソッドとして trim メソッドも定義した。 このメソッドは、XMLCh文字列の前後の空白を取り除く xercesc::XMLString::trim の機能を隠蔽したものだが、 これを使うと

void SampleHandler::characters(const XMLCh* const chars, const XMLSize_t length) {
  XMLCh* buffer=new XMLCh[XMLString::stringLen(chars)+1];
  XMLString::copyString(buffer, chars);
  XMLString::trim(buffer);
  char* content=XMLString::transcode(buffer);
  delete[] buffer;

  cout << "content: " << content << endl;
  XMLString::release(&content);
}

のように受け取った文字列の前後の空白を取り除いて出力するようなプログラムを、以下のように書けるようになる。

void SampleHandler::characters(const XMLCh* const chars, const XMLSize_t length) {
  cout << "content: " << transcode(XMLChPtr(chars).trim()) << endl;
}

注意点

XMLCh2CharTranscoder・Char2XMLChTranscoder クラスを使う際に

XMLCh2CharTranscoder も Char2XMLChTranscoder も両方ともクラスが持っているデストラクタの機能を使って、 XMLTranscoder を破棄している。 デストラクタが機能するときとは、変数がスコープから外れるときである。 従って、以下のようなプログラムを書くとエラーが起こる。

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

  XMLCh2CharTranscoder transcoder;

  // XMLCh2CharTranscoderを使った処理 ...

  XMLPlatformUtils::Terminate();

  return 0;
}

なぜなら transcoder 変数のスコープが、main 関数の終わりまでだからである。 このままでは生成した XMLTranscoder を破棄するのが、 XMLPlatformUtils::Terminate を呼び出した後になってしまう。 そこで以下のように書き換えることでスコープを抑えると問題は解決する。

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

  {
    XMLCh2CharTranscoder transcoder;

    // XMLCh2CharTranscoderを使った処理 ...
  }

  XMLPlatformUtils::Terminate();

  return 0;
}

コメント

コメントはありません。 コメント/xerces/capsulize/japanese

お名前: