連載
» 2001年06月16日 00時00分 公開

Javaで実現するDOM/SAXプログラミング(5):階層構造を持つXML文書をSAXで処理する (3/4)

[太田一郎,ティアイエス株式会社]

階層を構成している設定情報の場合

 今度は、XML文書を用いて階層的なデータ構造を表現しているような例を取り上げます。階層的なデータ構造といってもピンとこないかもしれませんが、それほど難しく考える必要はありません。

 例えば、皆さんがお使いのWindowシステム(例えばWindows)でおなじみのポップアップメニューのメニュー項目などがよい例でしょう。このポップアップメニューのメニュー項目を、XMLで表現することを考えてみます。

 図1に示すのは、Windowsのコンテキストメニューの一例です。単純に考えれば、リスト3のような形で表示されているメニュー項目を列挙するだけでよさそうです。

図1 ポップアップメニューの例 図1 ポップアップメニューの例
<?xml version="1.0" encoding="SHIFT_JIS" ?>
   
<menu>
   
  <item>
    <label>表示(V)</label>
  </item>
   
  <item>
    <label>アイコンの整列(I)</label>
  </item>
   
  <item>
    <label>等間隔に整列(L)</label>
  </item>
   
  <item>
    <label>最新の情報に更新(E)</label>
  </item>
   
  ……
   
</menu>
リスト3 メニュー項目を単純に列挙してみる

 メニューを表現しているのがmenuタグで、メニュー項目を表現しているのがitemタグです。いうまでもなくlabel>タグの文字列は、メニュー項目のラベルにあたります。

 しかし例えば、「アイコンの整列(I)」などは実際にはサブメニューになっています(図2)。

図2 サブメニューの例 図2 サブメニューの例

 こう考えると、リスト3のような形では少し不十分で、リスト4に示すようにmenu自体も項目として定義する必要があるでしょう。

<?xml version="1.0" encoding="SHIFT_JIS" ?>
   
<menu>
   
  <menu>
    <label>表示(V)</label>
    <item>
      <label>大きいアイコン(G)</label>
    </item>
   
    ……
   
  </menu>
   
  <menu>
    <label>アイコンの整列(I)</label>
    <item>
      <label>名前順(N)</label>
    </item>
    <item>
      <label>種類順(N)</label>
    </item>
    <item>
      <label>サイズ順(N)</label>
    </item>
    <item>
      <label>日付順(N)</label>
    </item>
    <item>
      <label>アイコンの自動整列(A)</label>
    </item>
  </menu>
   
  <menu>
    <label>等間隔に整列(L)</label>
  </menu>
   
  <item>
    <label>最新の情報に更新(E)</label>
  </item>
   
  ……
   
</menu>
リスト4 サブメニューも項目として取り扱う

 このように表現されているメニュー情報を読み込んで、最終的に図3に示すようなクラスのインスタンスで表現することに挑戦してみます。もちろん、SAXを用いてです。

図3 メニュー情報を格納するためのクラス 図3 メニュー情報を格納するためのクラス

 MenuItemがメニュー項目の情報を表し、Menuはそのリストを格納します。Menu自身がMenuItemの派生クラスになっているところがポイントです(この手の構造を表現するパターンとしては常とう手段ですね)。

 それぞれのクラスの詳細についてはここで説明しませんが、メソッドの名前からだいたい想像できるでしょう(詳細が知りたい方は、リスト5ソースを参照してください)。

 以下で基本的なアプローチについて述べた後、実際のソース(リスト5)について見ていきますが、その前に読者の皆さんは各自で、どのように実現するのか一度考えてみてください。

 基本的なアプローチは、以下のとおりです。

  • labelタグについては、JDBCの接続設定の例の場合と同じように扱う
  • menuやitemの情報を格納するためのインスタンスは、開始タグで生成し、終了タグで親メニューに追加

 少し工夫が必要なのは、「親メニューに追加」のところです。menuタグは入れ子にすることができます。menuの開始タグが新たに現れるたび、親メニューは変わることになりますが、終了タグが現れた時点で、変更前のものに戻さなくてはなりません。

 もうすでにお分かりの方もいるかと思いますが、こういう場合、スタックを用いて管理するのが適当です。具体的な使い方についてはソースを直接見た方が早いでしょう(リスト5)。

01: import org.xml.sax.*;
02: import org.xml.sax.helpers.*;
03: import java.util.*;
04:
05: public class MenuHandler extends DefaultHandler {
06:
07:   /** 親メニュー. */
08:   private Vector parents;
09:
10:   /** テキストデータの一時読み込み用バッファ. */
11:  private String text;
12:
13:  /** 処理中のメニュー. */
14:  private Menu menu;
15:
16:  /** 処理中のメニュー項目. */
17:  private MenuItem item;
18:
19:  /** ラベル. */
20:  private String label;
21:
22:  public void startDocument() {
23:    parents = new Vector();
24:    menu  = null;
25:    item  = null;
26:    label   = null;
27:  }
28:
29:  /*
30:  * 開始タグの処理.
31:  */
32:  public void startElement(String namespaceURI,
33:    String localName,
34:    String qName,
35:    Attributes atts) {
36:
37:      if (qName.equals("menu")) {
38:        if (menu != null) {
39:          parents.add(menu);
40:        }
41:        menu = new Menu();
42:      } else if (qName.equals("item")) {
43:        item = new MenuItem();
44:      } else if (qName.equals("label")) {
45:        label = null;
46:      }
47:    }
48:
49:  /*
50:  * 終了タグの処理.
51:  */
52:  public void endElement(String namespaceURI,
53:    String localName,
54:    String qName) {
55:
56:      if (qName.equals("menu")) {
57:        menu.setLabel(label);
58:        if (parents.size() > 0) {
59:          int  size   = parents.size();
60:          Menu parent = (Menu) parents.elementAt(size - 1);
61:          parents.removeElementAt(size - 1);
62:          parent.addItem(menu);
63:          menu = parent;
64:        }
65:      } else if (qName.equals("item")) {
66:        item.setLabel(label);
67:        menu.addItem(item);
68:      } else if (qName.equals("label")) {
69:        label = text;
70:      }
71:    }
72:
73:    /** テキストをバッファに格納. */
74:    public void characters(char[] ch, int start, int length) {
75:    text = new String(ch, start, length);
76:    }
77:
78:    /** 読み込んだメニューを返す. */
79:    public Menu getMenu() {
80:      return menu;
81:    }
82:  }
リスト5 メニュー情報を読むためのハンドラ(MenuHandler.java

 ソースを見ると分かるように、32行目のstartElementでタグの情報を格納するためのオブジェクトを生成し、52行目のendElementでそのオブジェクトを親メニューに格納するという処理をしています。

 ポイントは親メニューを格納するためのスタックparentsです。ここでは8行目にあるように、Vectorを用いてスタックを実現しています。startElementでmenuタグを認識した場合に、親メニューをスタックに格納していることを確認してください。

 このような入れ子の構造を処理する場合にスタックを用いるのは、SAXプログラミングにおける常とう手段の1つといえるでしょう。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。