TclXML

TclXMLは、Tcl言語とC言語で書かれた、XML文書の構文解析ルーチンのライブラリです。 Tcl本体と同じくオープンソースの体制で開発されており、 http://tclxml.sourceforge.net/ でプログラムや情報が入手できます。 現在のバージョンは3.2です。Tcl処理系のバージョンは8.3以降が推奨されています。 Tcl 8.1以降の国際化したTcl処理系を使えば、 元データとなるXML文書の文字列が正しく読めている限り、 日本語など非英語圏の言語で書かれたXML文書も正しく処理でき…ますが、 XML文書の文字コードがUTF-8以外の場合、Tclの内部で一旦UTF-8に変換するために、 エンコーディングの取り扱いには細心の注意が必要です。

TclXMLの開発プロジェクトは大きく3つ、 「tclxml」「tcldom」「tclxslt」のユニットに分けられます。 XML、DOM、XSLTの各用語をご存知の皆さんには全くの蛇足ですが、 一応説明しますと、 tclxmlはXML文書のパーサー(parser)=タグ等を解析するルーチンのライブラリです。 XML文書を最初からなめるように読みながら、タグに出会う度に何かの処理をする、 という方法で、一般にSAX(Simple API for XML) と呼ばれる処理モデルを実装したモジュールです。
tcldomはDOM(Document Object Model)と呼ばれる、 XML文書のデータを先頭から読むのではなく、 むしろXML要素のツリーをオブジェクトとしてアクセスするための、 プログラム言語に依存しないAPIを実装したライブラリです。
最後にtclxsltは、XSLT(XSL Transformations)プロセッサ =XML文書とXSLスタイルシート文書を結合して、HTML文書を生成するプログラム、 のライブラリです。

で、Tclのライブラリなのに、Tcl言語とC言語で書かれた、 というのはなんやねんと、いうわけですが、 XML文書の解析というのは、スクリプト言語でやるにはかなり重い処理ではあります。 また、SAXパーサ、DOMパーサ、XSLTプロセッサのライブラリとしては、 C言語で書かれたオープンソースの高品質なソフトウェアがいくつか知られています。 そこで、可搬性を優先してTcl言語だけで書いたパーサと、 C言語のオープンソースのライブラリの力を借りた、 高速なパーサ/プロセッサの何種類かをひとまとめにした、 というのがTclXMLプロジェクトの全貌というわけです。
tclxmlのパーサとしては、 現在tcl、libxml2の2つがサポートされています。
tclはTcl言語だけで書かれたSAXパーサです。 libxml2は The XML C parser and toolkit of Gnome というオープンソースのライブラリを利用したSAXパーサです。
なお、過去の一時期、上記以外に、James Clarkさんが開発した expatと、 Apache Software Foundationの Apache Xerces-C という、いずれもオープンソースのXMLパーサをサポートしたことがありましたが、 現在のホームページを見ると、これらへの言及がなくなっていることから、 最新版では除外されたものと思われます。
tcldomのパーサには、Tcl言語だけで書かれたものと、 The XML C parser and toolkit of Gnome のパーサの2種類があります。
そして、最も複雑なプログラムであるXSLTプロセッサを実装するtclxsltには、 Tcl言語のみの実装はありません。 tclxsltは The XML C parser and toolkit of Gnome のXSLTプロセッサ(libxsltとlibexslt)に依存しており、 これを事前にインストールしておく必要があります。


Windowsでは残念な結果に

このページの下のほうに、TclXML 3.0のソース配布をWindowsで無理やりコンパイルする方法を載せていますが、 バージョン3.2ではついに無理でした。(-_-)

  • ActiveTcl 8.5.8.0で配布されているTclXMLはver2.6で、最新のver3.2ではない。
  • ソース配布をMinGWでコンパイルするとエラー。 リンカーでlibxml2のシンボルが解決しません。 READMEでは「普通のTEA作法でコンパイルできるだろう」と書かれているのに…
  • makefile.vcを使って Visual C++ 2008(Visual Studio 2008)でコンパイルするとエラー。 こちらはあちこちで次々とエラーが発生し、 まともにNMAKEに読ませられる状態ではありません。 READMEでは「今のTclXMLはMinGWしか使ってないので、検証してないよ」 とはっきり書かれているので仕方ないですが、これは駄目だ…
というわけで、Linux PCを復活させて試すまで、3.2のインストール記録はありません。


日本語で書かれたXML文書は読める?

tclxmlに関していうと、どの文字コードで書かれたXML文書であっても、 一旦Tclのopenコマンドで文字列としてメモリに読んでから処理をします。 ということは、 XMLパーサに渡す文字列は既にUTF-8になっているわけなので、 Tcl処理系にopenコマンド(なりsocketなど他のコマンドなり)で正しく読ませることさえできれば、 XML文書をどの文字コードで書いてあっても一応、処理はできます。

一応、というのは、往々にしてXML文書の先頭には

<?xml version="1.0" encoding="Shift_JIS"?>

というXML宣言が入っているものですが、 ここに「Shift_JIS」「EUC-JP」などと書いていると、 メモリ上の文字列はUTF-8なのにエンコーディング指定は違うという、非常に珍妙なことになります。 tclxmlで処理させるXML文書はUTF-8で書くか、 このXML宣言にエンコーディングを書かない、という対策が必要になりましょう。


TclXMLのTclXML

TclXMLプロジェクトの「TclXML」「TclDOM」「TclXSLT」 の各アーカイブはそれぞれ同じサイトで別個に配布されています。 まずこのページでは、そのうちのTclXMLを使ってみます。

インストール(Windows ActiveTclの場合)
ActiveStateが配布しているTcl/Tk処理系、ActiveTclのバイナリ配布には、 コンパイル済みのTclXML+TclDOM+TclXSLTが同梱されているので、 インストールすればすぐに使えます。

インストール(Windows(MinGW)環境でApache Xerces以外の全てのパーサを試す場合)
ここではTclXML 3.0β2(他の2つも同じバージョン)、 MSYS 1.0.7 + MinGW 2.0.0、Tcl/Tk 8.4.6、Standard Tcl Library(tcllib) 1.6、 libiconv 1.7、libXML2 2.6.8、libXSLT 1.1.5 の環境でのメモを載せています。(ExpatはTclXMLのソース配布に同梱されています) libXML2とlibXSLTのインストール手順は こちらにメモっています。

TclXMLをインストールするには別段不要ですが、使用する際には Standard Tcl Library(tcllib)が必要になるので、 先にインストールしておく必要があります。 バージョンがあまり古いと使えないので、できるだけ新しい版を入手しましょう。

 TclXMLのソース配布アーカイブを展開した後のインストール手順は次の通りです。

  1. まず純粋Tclで書かれたパーサをインストールします。これは簡単です。

    $ sh configure; make; make install
    

  2. 次にexpatパーサをコンパイルするために、expatサブディレクトリに移動してコンフィギャーします。

    $ cd expat
    $ sh configure
    

    ここでMakefileの次の部分を書き換えます。

    #FILEMAP	=	unixfilemap
    FILEMAP	=	win32filemap
    
    #SHLIB_LD_LIBS	= ${LIBS} "/usr/local/lib/libtclstub84.a" -L/usr/local/lib/Tclxml3.0 -lTclxmlstub30
    SHLIB_LD_LIBS	= ${LIBS} "/usr/local/lib/libtclstub84.a" "/usr/local/lib/Tclxml3.0/Tclxmlstub30.a"
    

    そしてmake。

    $ make
    $ make install
    

  3. 次にlibxml2パーサですが、libxml2のライブラリ(DLL)の拡張子が、 .soでないとconfigureにうまく認識してもらえないようなので、 強引に対策します。

    $ cd /usr/local/lib
    $ ln -s libxml2.dll.a libxml2.so
    

  4. libxml2パーサをコンパイルするには、libxml2サブディレクトリに下りてから、 作業をします。

    $ cd libxml2
    $ sh configure --with-iconv-lib=/usr/local/lib \
      --with-libxml2-lib=/usr/local/lib
    

    ここでまたMakefileの次の部分を書き換えます。

    #SHLIB_LD_LIBS	= ${LIBS} "/usr/local/lib/libtclstub84.a" \
    # -L${LIBXML2_LIBDIR} -lxml2 -L/usr/local/lib/Tclxml3.0 -lTclxmlstub30
    SHLIB_LD_LIBS	= ${LIBS} "/usr/local/lib/libtclstub84.a" \
     -L${LIBXML2_LIBDIR} -lxml2 "/usr/local/lib/Tclxml3.0/Tclxmlstub30.a"
    

    しかるのちにmake。

    $ make
    $ make install
    

サンプルXML文書
さて、ここでは次のような注文伝票データをXML化した文書をサンプルとして使ってみましょう。

<?xml version="1.0" encoding="Shift_JIS"?>
<?xml-stylesheet type="text/xsl" href="orders.xsl"?>
<document>
  <process_time>2001-08-17 03:15</process_time>
  <order emflag="0">
    <order_number>10289246</order_number>
    <vendor_name>(株)鹿山電装</vendor_name>
    <order_date>2001-08-16</order_date>
    <line>
      <no>1</no>
      <item_code>CP80913027</item_code>
      <m_item_code>SFD1023-FS10.3AN30RK6-001</m_item_code>
      <name>カヘンテイコウキ</name>
      <quantity>30</quantity>
      <nbd>2001-09-04</nbd>
    </line>
    <line>
      <no>2</no>
      <item_code>CP10286513</item_code>
      <m_item_code>SFN1038-VM24X60.0AF-003</m_item_code>
      <name>チップテイコウ</name>
      <quantity>100</quantity>
      <nbd>2001-09-11</nbd>
    </line>
  </order>
</document>

このような業務データのXML表現は、 EDI(Electric Data Interchange)などでのデータ伝送に急激に普及してきています。 上の例は簡単のために項目を極端に絞りましたが、 内容としてはごくごくありがちなデータだと思います。


TclXMLのサンプル

ではではいよいよTclXMLを使って、 上のXML文書をタグに分解してみます。

package require xml
set filename [lindex $argv 0]

proc clearlog {} { set fout [open "tclxml.log" w]; close $fout }
proc loglog msg {
    set fout [open "tclxml.log" a]
    puts $fout $msg; close $fout
}
# タグ要素が開いたときに呼ばれます。
# タグ要素の名前、argsは属性があるときにはその属性名と属性値の
# リストからなるリストです。
proc estartCmd {hvar ename args} {
    upvar $hvar h
    lappend h $ename
    loglog "ESTART: $ename / $args"
}
# タグ要素が閉じたときに呼ばれます。
proc eendCmd {hvar ename args} {
    upvar $hvar h
    set h [lreplace $h end end]
    loglog "EEND: $ename / $args"
}
# データが現れたときに呼ばれます。
proc cdataCmd {hvar data} {
    upvar $hvar h
    if {[string length [string trim $data]] > 0} {
        loglog "CDATA: $data"
        set elementPath [join $h .]
        puts "$elementPath=\[$data\]"
    }
}
proc defaultCmd {args} { puts "DEFAULT: $args" }
# XMLファイルの内容を読み込みます。
set fin [open $filename r]; set buf [read $fin]; close $fin
# ログファイル初期化
clearlog

set h {}
set parser [::xml::parser -parser tcl \
 -characterdatacommand "cdataCmd h" \
 -elementstartcommand "estartCmd h" \
 -elementendcommand "eendCmd h" \
 -defaultcommand defaultCmd \
]
$parser parse $buf
# end.

TclXMLの簡単な使い方は、コマンド::xml::parserに読ませたい XML文書の全文が入った文字列を渡すだけです。 -parserオプションには現時点では「tcl」か「expat」かのパーサの種類を指定します。 (省略すると、expatつきでインストールされていればそちら、なければtclが使われます) 一見して気づくことは、解析処理はイベントドリブンで進む、ということです。 上の-characterdatacommand、-elementstartcommandなどのオプションで、 データやタグ要素に出会ったときに、それぞれどのような処理をさせたいかを記述した Tclスクリプトを指定します。 上の出力ですが、 「loglog」プロシージャを呼んでイベントが発生するたびにログを記録している部分のログを見てみると…

ESTART: document / {}
ESTART: process_time / {}
CDATA: 2001-08-17 03:15
EEND: process_time / 
ESTART: order / {emflag 0}
ESTART: order_number / {}
CDATA: 10289246
EEND: order_number / 
ESTART: vendor_name / {}
CDATA: (株)鹿山電装
EEND: vendor_name / 
ESTART: order_date / {}
CDATA: 2001-08-16
EEND: order_date / 
ESTART: line / {}
ESTART: no / {}
CDATA: 1
EEND: no / 
ESTART: item_code / {}
CDATA: CP80913027
EEND: item_code / 
ESTART: m_item_code / {}
CDATA: SFD1023-FS10.3AN30RK6-001
EEND: m_item_code / 
ESTART: name / {}
CDATA: カヘンテイコウキ
EEND: name / 
ESTART: quantity / {}
CDATA: 30
EEND: quantity / 
ESTART: nbd / {}
CDATA: 2001-09-04
EEND: nbd / 
EEND: line / 
ESTART: line / {}
ESTART: no / {}
CDATA: 2
EEND: no / 
ESTART: item_code / {}
CDATA: CP10286513
EEND: item_code / 
ESTART: m_item_code / {}
CDATA: SFN1038-VM24X60.0AF-003
EEND: m_item_code / 
ESTART: name / {}
CDATA: チップテイコウ
EEND: name / 
ESTART: quantity / {}
CDATA: 100
EEND: quantity / 
ESTART: nbd / {}
CDATA: 2001-09-11
EEND: nbd / 
EEND: line / 
EEND: order / 
EEND: document / 

な〜るほど、タグが現れる順番に書き出されていますね。 もう1か所、upvarコマンドを使って、 タグ要素が開いたり閉じたりするたびに、 現在のタグの階層構造を持たせている処理がありますが、 これは標準出力に、下のように出てくるはずです:

document.process_time=[2001-08-17 03:15]
document.order.order_number=[10289246]
document.order.vendor_name=[(株)鹿山電装]
document.order.order_date=[2001-08-16]
document.order.line.no=[1]
document.order.line.item_code=[CP80913027]
document.order.line.m_item_code=[SFD1023-FS10.3AN30RK6-001]
document.order.line.name=[カヘンテイコウキ]
document.order.line.quantity=[30]
document.order.line.nbd=[2001-09-04]
document.order.line.no=[2]
document.order.line.item_code=[CP10286513]
document.order.line.m_item_code=[SFN1038-VM24X60.0AF-003]
document.order.line.name=[チップテイコウ]
document.order.line.quantity=[100]
document.order.line.nbd=[2001-09-11]

おお、これだこれだ!(何が?)まずは、 上のなんとかCmdのところを工夫すれば、特定の値をもつデータだけを抽出したり、 データ間の集計、計算をさせることも可能だということが分かると思います。 このようなTcl言語でXMLデータを処理するルーチンは今後も徐々に充実してくるものと思われます。

拡張レビュー分室 top
(first uploaded 2001/08/16 last updated 2010/09/25, KOUKEN HEIJIMA - MISUMI URANO)