Delphi/Kylixで使えるオープンソースのXML Parser「Open XML」の使い方を説明します。

OpenXMLとは?
Delphi/Kylixで使えるオープンソースのXML Parser。
http://www.philo.de/xml/ からダウンロードできる

日本語対応について

  • 本家OpenXMLはUTF-8/ UTF-16には対応しているがShift_JISに対応していない。普通にSJISで書いたXMLを読み込ませるとパースエラーが出てしまう。
  • とりあえずShift_JIS読み込みができるようにcUnicodeCodecsを書き換えてみました。動いているように見えます。
    2005/1/20現在の最新版 「Utility Library v.2.0.3 」のcUnicodeCodecsにTShiftJISCodecを追加したものをこちら において置いておきます。

オリジナルから変更・追加されたのは以下の通り

  • TShiftJISCodecクラスを追加した。:
      type
        TShiftJISCodec = class(TCustomUnicodeCodec)
        protected
          procedure InternalReadUCS4Char(out C: UCS4Char; out ByteCount: Integer); override;
          procedure InternalWriteUCS4Char(const C: UCS4Char; out ByteCount: Integer); override;
        public
          procedure Decode(const Buf: Pointer; const BufSize: Integer; 
                 const DestBuf: Pointer; const DestSize: Integer;
                 out ProcessedBytes, DestLength: Integer); override;
          function  Encode(const S: PWideChar; const Length: Integer; 
                 out ProcessedChars: Integer): String; override;
        procedure WriteUCS4Char(const C: UCS4Char; out ByteCount: Integer); override;
       end;
    
  • TShiftJISCodecを参照するようテーブルにエントリを追加:
      const
        ShiftJISAliases = 4;
        ShiftJISAlias : Array[0..ShiftJISAliases - 1] of String = (
            'Shift_JIS', 'MS_Kanji', 'csShiftJIS', 'cp932');
    
      ....中略...
    
      const
        UnicodeCodecAliasEntries = 27;
        UnicodeCodecAliasList : Array[0..UnicodeCodecAliasEntries - 1] of UnicodeCodecAliasInfo =
          ((Table:@USASCIIAlias;     Count: USASCIIAliases;     Codec: TUSASCIICodec),
            (Table:@ISO8859_1Alias;   Count: ISO8859_1Aliases;   Codec: TISO8859_1Codec),
      ....中略....
            (Table:@UTF16LEAlias;     Count: UTF16LEAliases;     Codec: TUTF16LECodec),
            (Table:@ShiftJisAlias;    Count: ShiftJisAliases;     Codec: TShiftJisCodec)
          );
    

※実装の中身は、デフォルトのWideString ←→ AnsiString 変換に依存する「手抜き」コードです。日本語Windows環境ではたぶん動くような気がしています。(バグなり見つけたら教えてください)

インストール方法
普通に使うだけなら、ダウンロードしたUtilities_D?.dpk (XML Utilities)と、Xdom_3_1Delphi?.dpkを構築してインストールすればよい。
(実行時パッケージを作りたければXDOM_3_1_property_editor.pas, XDOM_3_1_Reg.pasの2ファイルを抜いて作成する必要がある。)

インストールされるコンポーネント

  • パレットはこんな感じに追加される
  • よく使われるコンポーネント

DomImplementationの意義

一般に、OpenXMLでは、TDomImplementationコンポーネントを置いて使う。(TDomToXml も TXmlToDomも、ともにDomImplementationプロパティをセットしないと動かない)
重要なことは、TXmlToDomが作成するTDomDocumentは、DomImplementationによって管理されるということである。すなわち、DomImplementationがFreeされるときに、管理リスト内のドキュメントを解放してくれる。DomImpl.を含むデータモジュールがすぐに解放されるのであれば、DomDocumentのスコープを意識してFreeする必要がない。

DomDocumentの解放の仕方

DomImplementationの管理下から先にドキュメントを引き剥がして削除するにはDomImplementation.freeDocumentメソッドを使う。これを呼ばずに直接Document.Freeをしてしまうと、DomImpl.のデストラクタの中で二重Freeとなってエラーが発生する。
なお、常に余分なドキュメントを持たないようにするには、全ドキュメントを解放するDomImplementation.clear;も使える

XMLの読み込み

  • ファイルから読み込む:
      var
        Doc: TdomDocument;
      begin
       Doc := XmlToDomParser.fileToDom(FileName);
    
       TXmlImplementationと一緒にTXmlToDomParserコンポーネントを配置。Parserのメンバメソッドを使う。
    
  • 文字列変数から読み込む:
      var
        Doc: TdomDocument;
      begin
        Doc := (XmlToDom.parseString(strXML, '', '', nil) as TDomDocument)
    

XPathで検索

Open XML を使うメリットとして、XPathが使えることは大きい。:

  //Doc: TDomDocument が与えられたとして...
  var
    root: TDomElement;
    exp: XPathExpression.Create;
    node: TDomNode;
    i: integer;
  begin
    root := Doc.documentElement;
    exp := XPathExpression.Create;
    exp.contextNode := root;
    exp.expression := '/root/something'; //ここにXPath式を入れる
    if exp.evaluate then begin
      for i := 0 to exp.resultlength-1 do begin
        node := exp.resultnode(i); //これが検索結果です!
      end;
    end;

  • 注意!!(めちゃくちゃハマル)

    OpenXMLのXPath検索は、NameSpace(名前空間)が宣言されているドキュメントでなければ名前検索が正しく動かない。
    名前空間なしのドキュメントでXPath検索が動くようにするには、TDomElement, TDomAttributeのコンストラクタに以下の変更を加える必要がある。:

        constructor TdomElement.create(const aOwner: TdomDocument;
                                   const tagName: wideString);
      begin
        if not IsXmlName(tagName)
          then raise EInvalid_Character_Err.create('Invalid character error.');
        inherited create(aOwner);
        FNodeName:= tagName;
        FLocalName := FNodeName; // K.Okada 2004/01/08 ← これを追加!
    

    同様にTDomAttr.Createにも以下の一行を加える(もちろんFNameが初期化された次の行):

        FLocalName := FNodeName;
    

XMLの作成・編集

  • ドキュメント新規作成:
      doc := DomImplementation.createDoc;
    
  • 要素追加・属性設定:
      var
        elem: TDomElement;
      begin
        elem := DomImplementation.createElement('element-name');
        elem.setAttribute('attribute-name', 'hogehoge');
        node.appendChild(elem);
    
  • 接ぎ木:
    サブノードを含めて全体につなぐ。:

      Document.importNode( SrcTreeTopNode, True);
    

※ ownerドキュメントが異なるノード同士はapendChildできない。かわりにimportNodeを使ってノード単独もしくはサブノードを再帰的に含めたコピーを行う。