C-7 多階層の XML 文書

ここまで作った検索プログラムで、例えば佐藤さんを探すために「佐藤」と入力して検索すると、

というように正しく検索されますが、石川さんを探すために「石川」と入力して検索すると

宮崎さんを探すために「宮崎」と入力して検索すると

というように余計なメンバーが引っ掛かり、思い通りの検索が出来ません。これは、MS-Excel で言えば、

というように各メンバーのデータの項目 (名前, 出身地, 電話番号) が区別されていない状態です。

この問題を解決するためには、

のように XML のデータ項目をより詳しく設定する必要があります (これは C-5 節ですでに扱いました)。MS-Excel で言えば

という状態に対応します。

このような XML文書を作るためには、データを入力する段階で

の3つを分けて入力する必要があります。

【練習問題】

まずは、(前節までに作成した) フォームを次のように作り変えて下さい。

コンポーネント Name プロパティ
「名前」エディット EditAddName
「出身地」エディット EditAddHomeLand
「電話番号」エディット EditAddPhone

次に、「追加」ボタンのイベントハンドラを変更します。プログラムを次のように変えて下さい。

// 「追加」ボタン
procedure TForm1.ButtonAddClick(Sender: TObject);
var
  MemberNode: Integer; // ノード番号を保持する変数
    // これによってノードの下にまたノードをぶらさげることができる
begin
  MemberNode := AddChild(Root, 'メンバー', ''); // ノード追加
    // そのノード番号を MemberNode に代入
  AddChild(Elements[MemberNode], '名前', EditAddName.Text); // 名前追加
  AddChild(Elements[MemberNode], '出身地', EditAddHomeLand.Text); // 出身地追加
  AddChild(Elements[MemberNode], '電話番号', EditAddPhone.Text); // 電話番号追加
  EditAddName.Text := ''; // 次の入力がやりやすいように欄の中身を消去
  EditAddHomeLand.Text := ''; // 次の入力がやりやすいように欄の中身を消去
  EditAddPhone.Text := ''; // 次の入力がやりやすいように欄の中身を消去
end;

同様に、「検索」ボタンのイベントハンドラも次のように変更します。

// 「検索」ボタン
procedure TForm1.ButtonSearchClick(Sender: TObject);
var
  MemberList: IXMLDOMNodeList; // メンバー取り出し用リスト変数
  Node: IXMLDOMElement; // メンバー取り出し用変数
  MemberNameList: IXMLDOMNodeList; // 名前取り出し用リスト変数
  MemberName: IXMLDOMElement; // 名前取り出し用変数
  MemberHomeLandList: IXMLDOMNodeList; // 出身地取り出し用リスト変数
  MemberHomeLand: IXMLDOMElement; // 出身地取り出し用変数
  MemberPhoneList: IXMLDOMNodeList; // 電話番号取り出し用リスト変数
  MemberPhone: IXMLDOMElement; // 電話番号取り出し用変数
  i: Integer; // for 文用カウンタ
begin
  // 「メンバー」要素を抽出、結果は複数個出てくる
  MemberList := Root.getElementsByTagName('メンバー');

  // 検索結果表示
  if (MemberList.length <> 0)
    then // 「メンバー」要素が1つでもあれば
      begin
        for i := 0 to MemberList.length - 1 do // 1要素ずつ
          begin
            // 「メンバー」ノードを取り出す
            Node := (MemberList.item[i] as IXMLDOMElement);
            // 名前を取り出す
            MemberNameList := Node.getElementsByTagName('名前');
            MemberName := (MemberNameList.item[0] as IXMLDOMElement);
            // 出身地を取り出す
            MemberHomeLandList := Node.getElementsByTagName('出身地');
            MemberHomeLand := (MemberHomeLandList.item[0] as IXMLDOMElement);
            // 電話番号を取り出す
            MemberPhoneList := Node.getElementsByTagName('電話番号');
            MemberPhone := (MemberPhoneList.item[0] as IXMLDOMElement);
            if (AnsiPos(EditSearch.Text, MemberName.text) > 0)
              then // 名前が含まれていれば
                begin
                  // 表示する
                  ShowMessage(MemberName.text
                    + MemberHomeLand.text + MemberPhone.text);
                end
            ;
          end;
      end
  ;
end;

解説

上のプログラムの検索部分で、例えば各メンバーから名前を取り出す部分

 MemberNameList := Node.getElementsByTagName('名前');
  MemberName := (MemberNameList.item[0] as IXMLDOMElement);
  
において、なぜ、わざわざ IXMLDOMNodeList 型の変数 MemberList に値を代入するのか、不思議に思った人がいるかもしれません。というのは、各メンバーには名前項目は一つしかないので、リスト型の変数ではなく直接 MemberName に代入しても良いように見えるからです。しかし、C-6 節で説明した通り getElementsByTagName 関数は、(IXMLDOMElement型ではなく) IXMLDOMNodeList 型の値を返すため、たとえ値が一つでも一旦は、IXMLDOMNodeList型の変数に代入する必要があるのです。

ここまでのプログラムをダウンロード(D)

注意 C-6 節でインストールした「Microsoft XML」のバージョンが 2.0 だった人は、上でダウンロードしたプログラムのユニットにある uses 節内の MSXML2_TLBMSXML_TLB にしてください ("2"を取る)。

【発展課題 C-2】

このように項目を正しく検索するプログラムを、メモを使った (XML を使っていない) C-3節のプログラムから作るとしたらどのように処理したらよいか考え、その方法を述べて下さい(実際にプログラミングしなくても結構です)。

コラム  XML の柔軟性

この発展課題をよく考えると、メモを使ったプログラムで複雑なデータ構造を扱うことがどんなに大変か、よく分かったと思います。一方、XML を使ったプログラムでは、「メンバーデータ」の中に「名簿作成者」「名簿作成年月日」という要素を入れたり、「メンバー」の中に「性別」「郵便番号」「生年月日」という項目を増やしたりしても、このプログラムのままで正しく動作します。XML を用いたプログラムは、柔軟性に富み、データ仕様の変更に容易に対応できます。