Tcl/Tk で XML の DOM ツリーを表示する

ここでは Tcl/Tk を使って XML 文書の DOM ツリーを表示する短いプログラムを作成し、解説していきます。 XML の解析には TclDOM を、ツリー表示には BWidget に含まれる Tree ウィジェットを使います。 これらの拡張は ActiveTcl のパッケージには両方とも含まれています。

TclDOM は Tcl から DOM を扱うための拡張で、 XML 文書をツリー構造として扱うことができるようになります。 特徴として Tcl だけで実装された XML パーサを使うことが出来るため、 ポータビリティが高いということがあげられます。 TclDOM は DOM Level 1 (と Level 2, 3 の一部)を実装していますが、 TclDOM のリファレンスには DOM API 自体の説明が乏しいので、 必要に応じて DOM Level 1 specification を参照するとよいでしょう。 日本語版の DOM 1 仕様書を利用することも出来ます。

comp.lang.tcl のスレッド TclDOM2.4: Any issues compared with 2.0 によると、 2002-12-27 の時点で ActiveTcl の最新版として配布されている ActiveTcl 8.3.5.0 と ActiveTcl 8.4.1.0 に含まれる TclDOM はバグを含んでおり、このため当ページのプログラムも実行できません。 このページのプログラムは Windows 用の ActiveTcl 8.3.4.4 および Linux 用の ActiveTcl 8.3.4.3 に含まれる TclDOM で動作を確認しています。

サンプルプログラム

今回作成したサンプルプログラムは以下のようなものです。

package require dom
package require BWidget

menu .menu
.menu add command -label {Open} -command {
  global id
  set filename [tk_getOpenFile -filetypes {{{XML Files} {.xml}}}]
  if {$filename != ""} {
    set f [open $filename r]
    fconfigure $f -encoding utf-8
    set doc [dom::DOMImplementation parse [read $f]] ;# XML の解析
    close $f
    dom::DOMImplementation trim $doc ;# トリム
    set id 0
    .tree delete [.tree nodes root] ;# Tree ウィジェットの初期化
    addnodes root [dom::document cget $doc -documentElement]
  }
}
. configure -menu .menu
Tree .tree -width 50 -height 30 \
  -xscrollcommand {.sh set} -yscrollcommand {.sv set}
scrollbar .sv -command {.tree yview} -orient vertical
scrollbar .sh -command {.tree xview} -orient horizontal
grid .tree -row 0 -column 0 -sticky news
grid .sv  -row 0 -column 1 -sticky ns
grid .sh -row 1 -column 0 -sticky we
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1

proc addnodes {parent node} {
  global id
  set this [incr id]
  set nodevalue [dom::node cget $node -nodeValue]
  set nodename [dom::node cget $node -nodeName]
  switch $nodename {
    #text {
      set label $nodevalue
      regsub -all {\s+} $label { } label
      set color black
    }
    default {
      set label $nodename
      set color red
    }
  }
  .tree insert end $parent $this -text $label -open 1 -fill $color
  foreach {name value} [array get [dom::node cget $node -attributes]] {
    .tree insert end $this [incr id] \
      -text "$name=\"$value\"" -open 1 -fill blue
  }
  if {[dom::node cget $node -nodeName] == "#text"} return
  set child [dom::node cget $node -firstChild]
  while {$child != ""} {
    addnodes $this $child
    set child [dom::node cget $child -nextSibling]
  }
}

プログラムの解説

Tree ウィジェットの使いかた

BWidget に含まれるウィジェットは package require BWidget とすることで使用できるようになります。 Tree ウィジェットは標準の Tk ウィジェットと同様に

Tree pathName ?option value...?

で作ることができ、その後 packgrid などでレイアウトします。 使えるオプションの詳細は BWidgets のドキュメントを参照してください。 今回のサンプルプログラムではウィジェットのサイズとスクロールバーとの結び付けを指定しています。

Tree ウィジェット内の全てのノードには名前が付けられますが、 最上位のノードにはあらかじめ root という名前が付けられています(このノードは表示されません)。

ツリーにノードを追加するには以下の書式を使います。

pathName insert indexparentnode ?option value...? 

parent で親ノードの名前を、 node で新しく追加する子ノードの名前を与えます。 index で親ノードの子リストの何番目に新しい子ノードを追加するかを指定します。 先頭に追加するなら 0 、末尾に追加するなら end を指定します。 option value... で指定できるオプションのうち、 今回のプログラムで使用したものは以下の三つです。

-text
ノードのラベルに使う文字列を指定します
-open
ノードを開いた状態で表示するなら 1 、閉じた状態で表示するなら 0 を指定します。
-fill
ノードの文字列の色を指定します。

XML の解析

dom::DOMImplementation parse data

このコマンドの引数 data に XML テキストを与えると DOM ツリーが作られ、 戻り値としてルートノードのトークンが返されます。

トリム

サンプルプログラムでは XML を解析したあと、 dom::DOMImplementation trim $doc というコマンドを実行しているのですが、 これはホワイトスペースのみからなるテキストノードを取り払うコマンドです。 例として以下の二つの XML 文書を比較して考えましょう。

<root><foo/></root>
<root>
<foo/>
</root>

前者の XML から DOM ツリーを作った場合、 root 要素の子ノードは foo 要素のみになります。 一方後者のようなタグが改行で区切られた XML の場合、 root の子ノードは 順番に、 改行のみのテキストノード、 foo 要素、改行のみのテキストノード、となります。 dom::DOMImplementation trim token というコマンドを通しておくと、 後者の場合も前者と同じ DOM ツリーが得られるわけです。

再帰的なツリー構築

addnodes プロシージャは第一引数で指定した名前の Tree ウィジェットのノードの下に 第二引数で指定したノードを追加します。 第一引数の parent には Tree ノード内の名前を、第二引数の node には TclDOM のトークンを指定します。

node がさらに子を持っている場合は addnodes の中で再帰的に addnodes を呼び出します。

Tree ウィジェット内のノード名が重複しないようにグローバル変数 id を一つずつ増やし、 それを名前として使っています。

ノードがテキストノードの場合は黒でそのテキストの値を表示し、 要素の場合は赤で要素名を表示します。 要素が属性を持っている場合は青でその要素の子として表示します。

実行例

このプログラムで rei harakami online の RSS ファイルを表示した例は以下のようになります。

RSS ファイルの内容がツリー表示され、要素名が赤、属性が青、テキストノードが黒で色分けされている
http://nul.jp/2002/tcl_dom
文書作成: 2002-12-26