C++BuilderでMSXML6を使用する

C++Builder標準のTXMLDocumentはXPathが使えないなど微妙に使いづらいので、MSXMLのタイプライブラリをインポートして使ってみる。

まず、以下の手順でMSXMLのタイプライブラリをインポートする。

  1. [コンポーネントコンポーネントのインポート]を選択
  2. "VCL for C++ Win32"を選択して[次へ]をクリック
  3. タイプライブラリの取り込み
  4. "Microsoft XML, v6.0"を選択して[追加]をクリック *1
  5. タイプライブラリの出力先を入力。デフォルトは、マイドキュメント\RAD Studio\7.0\Imports\*2
  6. [次へ]をクリック
  7. ユニットの作成を選択して[完了]をクリック
  8. 出力先にMSXML2_TLB.cpp、MSXML2_TLB.h、MSXML2_OCX.dcrが生成される

実際に使う場合は、プロジェクトに追加するだけでOK。

  1. [プロジェクト|オプション]を選択
  2. インクルードパスにタイプライブラリの出力先を追加
  3. MSXML2_TLB.hをインクルード
  4. プロジェクトに$(BDSUSERDIR)\Imports\MSXML2_TLB.cppを追加

XMLファイルをパースするコードは以下の通り。

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HRESULT hr;

  IXMLDOMDocumentPtr pDoc;
  hr = pDoc.CreateInstance(CLSID_DOMDocument);
  _ASSERTE(hr);
  pDoc->async = VARIANT_FALSE;

  // XMLファイルのロード
  short r;
  hr = pDoc->load(Variant("TEST1.XML"), &r);
  if(hr != S_OK || !r){
    // エラー処理
    ShowMessage(_T("LOAD ERROR"));
    return;
  }

  ShowMessage(pDoc->documentElement->tagName);
}

XMLファイルを保存する場合はIXMLDOMDocument::saveでもいいのだけれど、インデントしてくれないのでIMXWriterを使って保存する。

bool SaveXML(IXMLDOMDocumentPtr pDoc, UnicodeString strPath)
{
  TFileStream* pFS = new TFileStream(strPath, fmCreate);
  TStreamAdapter* pSA = new TStreamAdapter(pFS, soOwned); // TStreamAdapterはTStreamとIStreamの仲介役

  HRESULT hr;

  IMXWriterPtr pWriter;
  hr = pWriter.CreateInstance(CLSID_msMXXMLWriter);
  _ASSERTE(hr == S_OK);
  pWriter->version = L"1.0";
  pWriter->encoding = L"UTF-8"; // 文字コードを指定
  pWriter->indent = VARIANT_TRUE; // TRUEでは駄目

  IStream* pIS = *pSA;  //  TFileStreamに付加したIStreamインターフェースを取得
  pWriter->set_output(Variant(pIS));  // XMLの出力先をTFileStreamにする

  ISAXXMLReaderPtr pReader;
  pReader.CreateInstance(CLSID_msSAXXMLReader);
  hr = pReader->putContentHandler((ISAXContentHandlerPtr)pWriter);
  _ASSERTE(hr == S_OK);
  hr = pReader->parse(Variant((IUnknown*)pDoc));
  _ASSERTE(hr == S_OK);

  return true;
}

ファイルの保存はTFileStreamを使用する。IMXWriterは出力先にIStreamインターフェースが必要なので、TStreamにIStreamインターフェースを付加してくれるTStreamAdapterを使用する。
TStreamAdapterはアプリケーションで明示的に破棄する必要はなく、TFileStreamもsoOwnedでTStreamAdapterと結びつけているので、TFileStreamの破棄も不要。*3
COMオブジェクトに渡す論理型(VARIANT_BOOL型)は、windef.hで定義されているTRUE/FALSEと違うので要注意。*4

*1:検索ボックスに"XML"と入力すれば見つけやすい。

*2:プロジェクト内では$(BDSUSERDIR)\Imports\で参照できる。

*3:http://docwiki.embarcadero.com/VCL/ja/Classes.TStreamAdapter.Destroy

*4:http://slashdot.jp/~A7M/journal/490057