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を使ってノード単独もしくはサブノードを再帰的に含めたコピーを行う。