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

Xerces-CにICUを使わせるには

使えない文字コード

Apache Xerces-Cは最も広く用いられているC++版XML-parserのひとつです。 Apache XMLのwebサイトhttp:://xml.apache.org/からリンクをたどればXerces-CのWin32バイナリ版が入手でき、Visual C++ 6.0で利用できます。 ですがこのバイナリ版にはひとつだけ困った問題があります。

Win32バイナリ版Xerces-Cをインストールし、その中のサンプル’DOMPrint’を使ってXMLをプリントしてみます。このときプリント対象となるXMLがshift_jisあるいはUNICODE(UTF-16/UTF-8)であれば何の問題もないのですが、たとえばeuc-jpやiso-2022-jpで書かれたDOMを正しくparseすることができません。

様々なencodingで書かれたXMLをプリントすると…

shift-jis
domprint -x=shift_jis sjis.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>この文字列はshift_jisです</message>
utf-16
domprint -x=shift_jis utf.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>この文字列はUTF-16です</message>
utf-8
domprint -x=shift_jis utf8.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>この文字列はUTF-8です</message>
euc-jp
domprint -x=shift_jis euc.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>、ウ、ホハクサホ、マeuc-jp、ヌ、ケ</message>[1]
iso-2022-jp(JIS)
domprint -x=shift_jis jis.xml
Fatal Error at file "jis.xml", line 2, column 11
   Message: Invalid character (Unicode: 0x1B)
An error occured during parsing

入力文字列をUNICODEに変換する際、XML-parserが内部で利用しているWindows-APIがiso-2022-jpやeuc-jpに対応していないからです。

Xerces-Cに添付されているHTMLドキュメントには文字コード変換にIBMのICUを利用するようにライブラリを修正する手順が明記されていますが、それによるとperlおよびUNIX-コンパチブルなshellを必要とします。ここではperlやshellを使わずにVisual C++ IDEでXerces-CをICU対応させる手順を紹介します。

※ 以下に示す手順は、2001年5月現在入手可能な最新版:

  • Xerces-C 1.4.0
  • ICU 1.8

に対応しています。

作業手順

  1. ICUのインストールとビルド

    IBM-ICUのサイトからICU1.8をダウンロードし、適当なディレクトリに展開します。以降、ICUを展開したディレクトリを<ICU>と表します(ここでは m:\icu とします)。ICU-distributionに含まれる<ICU>\source\allinone\allinone.dswをVC++IDEで開き、プロジェクト’all’のRelease/Debugライブラリ(DLL)をビルドします。

    ビルドが完了したら<ICU>\bin\icuuc.dll, <ICU>\bin\icuucd.dllおよび<ICU>\source\data\icudt18l.dllをPATHの通ったディレクトリにコピーしておきます。

  2. Xerces-C Win32バイナリ版のインストール

    Apache XMLのサイトからXerces-C 1.4.0 Win32バイナリ版をダウンロードし、適当なディレクトリに展開します。以降、Xerces-C Win32バイナリ版を展開したディレクトリを<XERCES>と表します。

  3. Xerces-C Win32ソースコード版のインストール

    Apache XMLのサイトからXerces-C 1.4.0 ソースコード版をダウンロードし、適当なディレクトリに展開します。以降、Xerces-C ソースコード版を展開したディレクトリを<XERCESSRC>と表します。

  4. Xerces-C プロジェクト設定の変更

    <XERCESSRC>\D:\Projects\Win32\VC6\xerces-all\xerces-all.dswをVC++IDEで開き、プロジェクト設定を以下のとおりに変更します。

    • インクルード・パス <ICU>\include を追加します。
    • プリプロセッサ・マクロ XML_USE_WIN32_TRANSCODERXML_USE_ICU_TRANSCODER に書き換えます。
    • プロジェクトの設定、C/C++
    • ライブラリ・パス <ICU>\lib および <ICU>\source\data を追加します。
    • ライブラリ icuuc.lib(Debug版はicuucd.lib) および icudata.lib を追加します。
    • プロジェクトの設定、リンク
    • ワークスペースから Win32TransService.hpp, Win32TransService.cpp を削除し、<XERCESSRC>\src\util\Transcoders\ICU\ICUTransService.hpp, <XERCESSRC>\src\util\Transcoders\ICU\ICUTransService.cpp を追加します。
    • ワークスペース
  5. プロジェクト ‘XercesLib’ のRelease/Debug版をビルドします。

    ビルドが完了すれば <XERCESSRC>\Build\Win32\Vc6\Release および <XERCESSRC>\Build\Win32\Vc6\Debug

    • xerces-c_1.lib, xerces-c_1_4.dll (Release版)
    • xerces-c_1D.lib, xerces-c_1_4D.dll (Debug版)

    が生成されています。これらを <XERCES>\lib, <XERCES>\bin にコピーします。

確認

さて、ICU-enabled な Xerces-C の構築に成功したか、さきほどうまくいかなかったencodingによるXMLを再度 DOMPrint に食わせてみましょう。

euc-jp

domprint -x=shift_jis euc.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>この文字列はeuc-jpです</message>

iso-2022-jp(JIS)

domprint -x=shift_jis jis.xml
<?xml version="1.0" encoding="shift_jis"?>

<message>この文字列はiso2022-jpです</message>

ちょっとした応用を考えてみましょう。XML-parserがiso-2022-jpを受け付けてくれるので、XMLドキュメントをメール(SMTP)に投げ、POP3で受け取ってparseすることができるはずです。

適当なメールアカウントに対し

<?xml version='1.0'
encoding='iso-2022-jp' ?>
<message>このメッセージは電子メールに投げられたXMLから取り出されました</message>

を送信し、以下のプログラムを起動すると、<message>タグに囲まれたテキスト

このメッセージは電子メールに投げられたXMLから取り出されました

がコンソールに出力されます

// --- StdLib. (SourcePro Core)
#include <iostream>
#include <sstream>
#include <locale>

// --- SourcePro Network
#include <rw/network/RWSocketPortal.h>       // RWSocketPortal
#include <rw/network/RWPortalIStream.h>      // RWPortalIStream
#include <rw/network/RWWinSockInfo.h>        // RWWinSockInfo
#include <rw/internet/util.h>                // rwNormalizeLine()
#include <rw/internet/RWStreamCoupler.h>     // RWStreamCoupler
#include <rw/pop3/RWPop3Agent.h>             // RWPop3Agent

// --- Xerces-C
#include <util/PlatformUtils.hpp>            // PlatformUtils
#include <parsers/DOMParser.hpp>             // DOMParser
#include <sax/ErrorHandler.hpp>              // ErrorHandler
#include <sax/SAXParseException.hpp>         // SAXParseException
#include <dom/DOM.hpp>                       // DOM_xxx
#include <framework/MemBufInputSource.hpp>   // MemBufInputSource

/*
 * definitions
 */

#define POP3_SVR "POP3サーバアドレス"
#define POP3_USR "ユーザ名"
#define POP3_PWD "パスワード"

/*
 * namespace
 */
using namespace std;

/*
 * ErrorHandler for DOMParser
 */
class ErrorReporter : public ErrorHandler {
  bool ok_;
public:
  ErrorReporter() : ok_(true) {}
  ~ErrorReporter() {}
  virtual void warning(const SAXParseException& ex)
    { }
  virtual void error(const SAXParseException& ex)
    { ok_ = false; }
  virtual void fatalError(const SAXParseException& ex)
    { ok_ = false; }
  virtual void resetErrors() { ok_ = true; }
  bool ok() const { return ok_; }
};

/*
 * XML-documentをparseし、
 * ドキュメント中の 'message' タグの内容を表示する
 */
int parse_message(const string& message) {
  DOMParser parser;
  ErrorReporter errReporter;
  parser.setErrorHandler(&errReporter);
  parser.setExitOnFirstFatalError(true);
  MemBufInputSource source(reinterpret_cast<const unsigned char*>(message.data()),
                           message.length(),
                           L"application/xml");
  parser.parse(source);
  if ( !errReporter.ok() ) {
    return 1;
  }
  DOM_Document document = parser.getDocument();
  DOM_NodeList nodes = document.getElementsByTagName("message");
  for ( unsigned i = 0; i < nodes.getLength(); ++i ) {
    DOM_Node node = nodes.item(i);
    if ( node.getNodeType() != DOM_Node::ELEMENT_NODE ) {
      continue;
    }
    char* message = node.getFirstChild().getNodeValue().transcode();
    cout << message << endl;
    delete[] message;
  }
  return 0;
}

/*
 * POP3からメールを取り出し、メッセージをparse_message()へ
 */
int get_mail() {
  RWPop3Agent agent(POP3_SVR, POP3_USR, POP3_PWD);
  int n = agent.messages();
  for ( int i = 1; i <= n; ++i ) {
    RWSocketPortal portal = agent.get(i);
    RWPortalIStream istrm(portal);
    RWCString text;
    bool body = false;
    do {
      text.readLine(istrm, false);
      text = rwNormalizeLine(text);
      if ( text.isNull() ) {
        body = true;
        break;
      }
    } while ( text != "." );
    ostringstream ostrm;
    if ( body ) {
      RWStreamCoupler couple;
      couple(istrm, ostrm, pop3StreamFilter);
      agent.removeMessage(i);
      parse_message(ostrm.str());
    }
  }
  return 0;
}

/*
 * メイン
 */
int main(int argc, char* argv[]) {
  locale::global(locale("japanese"));
  try {
    RWWinSockInfo info;
    XMLPlatformUtils::Initialize();
    get_mail();
  } catch ( RWxmsg& rwer) {
    cerr << "Exception : " << rwer.why() << endl;
  } catch ( std::exception& stder ) {
    cerr << stder.what() << endl;
  }
  XMLPlatformUtils::Terminate();
  return 0;
}

編集部注

  1. ^ 文字化けした結果をWebサイト上で表現するために、実際の出力を一部加工しています。