iPEX(アイペックス)コラム:XMLでデータを自由に扱うには

XMLでデータを自由に扱うにはXMLは重要といわれていますが,なぜ重要なのか,かんたんに説明することは難しかったりします。XMLの生い立ちに振り返ってみると,データをいかに効率よく意味や価値を失わずに再利用していくかというところにたどり着きます。本稿ではプログラマにとってXMLとはなにかを解説します。

インフォテリア(株) 奥山裕之(おくやまひろゆき)
技術評論社 "PC Programing Vol.1"より抜粋

はじめに

ここ1年くらいのことだと思うのですが,数々の雑誌で「XML(Extensible Markup Language)」という言葉を目にするようになりました。「XMLがビジネスを変える」だとか,「XMLが実現するB2Bの世界」だとか。ある種のキーワードのごとく,数々の記事のタイトルに冠されています(B2B:Business to Business:企業同士の通信)。しかし,このXMLとはどういったものなのでしょうか。XMLを使うと,なぜビジネスが変わるのでしょうか。XMLで何ができるのでしょうか,本稿ではXMLを利用する理由,そしてXMLを利用するためのプログラミングについて,基礎的なところを説明していきたいと思います。

HTMLとXML ~ データとデザインの分離

いきなりXMLについて語る前に,まずはすでに皆さん慣れ親しんでいるであろうHTMLから内容に入っていきたいと思います。HTMLはご存知のように,インターネットの世界でWebページを作成するのに使用されている言語です。Hyper Text Markup Languageという言葉が示すように,内容をMarkupする,わかりやすくいえばタグで修飾することで,さまざまな表現を可能にしています。

たとえば,リスト1をご覧ください。

◆リスト1◆ HTMLファイルサンプル
<HTML>
  <HEAD>
  <TITLE>Simple address table</TITLE>
 </HEAD>
 <BODY>
  <TABLE BORDER="1">
   <TR>
    <TH><B>Name</B></TH>
    <TH><B>Phone</B></TH>
   </TR>
   <TR>
    <TD>Hiroyuki Okuyama</TD>
    <TD>042- 390- 2903</TD>
   </TR>
   <TR>
    <TD>Takashi Yamamoto</TD>
    <TD>03-9083-4393</TD>
   </TR>
  </TABLE>
 </BODY>
</HTML>

名前や電話番号が<TD></TD>というタグで囲まれています。ほかにも<BODY>,<TABLE>などといったいくつかのタグが現れます。このようにタグで修飾することで,HTMLを表示するためのソフトであるWebブラウザは,単なるテキストを表の形式にして表示してくれます(図1)。

リスト1をブラウザに表示したイメージ図

図1Webブラウザは,単なるテキストを表の形式にして表示してくれます。

さて,唐突ですがここ でちょっとリスト1のHTMLから必要なデータを引き出すことを考えてみましょう。たとえば,取引のある会社の電話番号を自分のPIM(スケジューラー・アドレス帳)に入力するなんてことはよくあります。その電話番号が部署ごとに異なっており,一覧がインターネットで公開されている場合には,通常手作業でそれぞれの電話番号を読み取り,入力していきます。

この作業はわれわれ人間にとっては,そんなに難しいことではありません。リスト1の場合には項目も少ないですし,一瞬見るだけで"042-390-2903"のような番号が電話番号であることを経験的に判断できるからです。

HTMLファイルからデータを取り出す

しかし,もしこのリスト1が膨大な人数の電話番号を持つテーブル(表)だった場合どうなるでしょうか。人の手でデータを抽出するのは大変な作業になってしまいます。それならば当然ここでコンピュータで処理しようと考えます。しかし,コンピュータはどういった文字列は名前で,どのような数字の並びは電話番号だ,ということを判別するのはあまり得意ではありません。このため,ある規則を規定して,コンピュータに処理させることになります。今回のリスト1では,1番目の項目が名前で2番目が電話番号である,といった規則が成り立ちます。しかし,いったんこのような規則を決めてしまうと住所の項目が増えたとき,順番が変更された場合,といったときに対応できなくなり,柔軟性に欠けることになります。またそれとは別に,HTMLそのものを扱うことに関していえば,データとは関係のないタグが入っているので,その中から必要なデータだけ探し出すのは効率がよいとはいえません(今回の例ではそれほどでもないですが見栄えのよいWebを作ろうとすれば,デザインのためのタグが膨大に入ることになります)。これが,すなわちデータとデザインが同じファイルの中で混在している,ということです。

このように,HTMLだけで作られているWebページからデータの再利用をしようとするといろいろと問題があることがおわかりかと思います。リスト1のように名前と電話番号だけのリストでも,その内容を取り込んでほかのアプリケーションで利用できたら便利なのですが,それが簡単にはできないのです。

XMLの登場

今度は逆にデータを取り出すのではなくて,そもそものデータをデザインから切り離して用意しておいて,そこからHTMLを作り出すという仕組みを考えます。そうすれば,データの種類,順番が変更しても問題なく処理することができます。データを再利用する人は,そのデータそのものをもらえば済んでしまいます。しかし,これくらいのことは誰でも考えそうなことで,データからHTMLを自動生成するシステムはたくさんあります。とはいえ,今までのそういったシステムではデータの再利用が簡単ではないのが実情です。なぜなら,そのデータはたとえばデータベース上のデータであったりして独自の形式になっているからです。そういったデータをクライアントが利用するにはデータを標準的な形式に変換する必要があります(この標準的,というのもまた問題なのですが)。となると,データを提供する側はまずHTMLを作り,さらに変換したデータを用意する必要があります。

そこで,このデータ形式としてXMLが登場します。たとえばリスト1のデータをXMLで表現してみましょう。リスト2がその例です。

◆リスト2◆ HTMLファイルサンプル
<?xml version="1.0"encoding="Shift_JIS"?>
<addressbook>
 <owner>Hiroyuki Okuyama</owner>
 <item>
  <index>hiroyukiokuyama</index>
  <name>Hiroyuki Okuyama</name>
  <phone>042- 390- 2903</phone>
 </item>
 <item>
  <index>takashiyamamoto</index>
  <name>Takashi Yamamoto</name>
  <phone>03-9083-4393</phone>
 </item>
</addressbook>

非常にシンプルにでき上がっているのがわかると思います。データを表現するためのものなので,デザインのための余分なタグは入ってませんし,それぞれのタグも,タグそのものが囲っているデータの内容を指し示しているため,人間が見てもデータの内容が一目瞭然です。

リスト2をブラウザに表示したイメージ図

図2リスト2をブラウザで表示すると図1と同じになる

XMLの特徴

さらにXMLはデータ表現の柔軟性,多様性を備えています。リスト2に<index></index>で囲った項目(XMLではエレメント,もしくは要素という)を用意することで,データをソートすることもできるでしょう。また,addressという住所を入れる項目を追加したとしても,データを利用するアプリケーションにとっては,addressという項目を知らなければそのタグを無視し,addressが必要ならば,必要に応じて取り出すことができるのです。

このようにXMLはデータ形式としては非常に単純である上に,ユーザーが自由にタグを定義してフォーマットを拡張することができるのです。また,後半で解説しますが,XMLにはデータにアクセスするためのインターフェイスが規定されていることもあり,さまざまに異なるプラットフォームで同様の手順でデータを利用することができます。

また,XMLはその関連技術であるXSLT(XSL Transformations XSLはeXtensible Stylesheet Languageの略)を使用することで簡単にHTMLを生成することができます。今回はXSLについての解説は行いませんが,参考までにリスト3にその例を載せておきます。リスト2の2行目に

<?xml- stylesheet type="text/xsl" href="address.xsl"?>

と書き加え(リスト3をリスト2と同じディレクトリにaddress.xslという名前で保存した場合),ブラウザで表示すると図2のようになります(注次項コラム参照)。これは図1と同じになっているのが分かると思います。また,もとになるXMLのデータが異なっていても,その構造が同じであれば同じXSLを作用させることで,同じ形式のHTMLを生成することができるのです。

◆リスト3◆
<?xml version="1.0"encoding="UTF- 8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="1.0">
 <xsl:output method="html"indent="yes"/>
 <xsl:strip- space elements="*"/>
 <xsl:template match="/">
 <HTML>
  <HEAD>
   <TITLE>Simple address table</TITLE>
  </HEAD>
  <BODY>
   <TABLE BORDER="1">
    <TR>
     <TD><B>Name</B></TD>
     <TD><B>Phone</B></TD>
    </TR>
    <xsl:for- each select="/addressbook/item">
     <xsl:sort select="index"/>
     <TR>
      <TD>
       <xsl:apply-templates select="name"/>
      </TD>
      <TD>
       <xsl:apply-templates select="phone"/>
      </TD>
     </TR>
     </xsl:for-each>
    </TABLE>
   </BODY>
  </HTML>
 </xsl:template>
</xsl:stylesheet>

XMLをプログラミングから扱うには~ XMLパーサーとDOMについて

今度はXMLからデータを取り出すためのプログラミングについて触れてみましょう。XMLを扱うためにはいくつかの方法がありますが,今回はその中からDOMインターフェイスを備えたXMLパーサーを使用します。と,ここまでさらりと書きましたが,この中にはいくつかのキーワードが潜んでいます。DOM,インターフェイス,パーサーといった言葉がそれです。XMLでのプログ ラミングをするにあたって,これらの理解は欠かせませんので,説明をしておきます。

文字列を解釈するパーサー

まず,パーサー(Parser)です。前節でHTMLでのテーブル表示について触れましたが,皆さんはHTMLが単なるテキストであるのに,ブラウザがどのようにしてテーブルであるとか,フレームであるとかいったような表示ができているか,考えたことはないでしょうか。実は,それこそがパーサーの役割です。

パーサーとは,ある文法規則にのっとった文字列を,その文法に基づいて字句解析し,意味や構造を解釈するソフトウェアです。ちょっと難しいですが,要するに,<TABLE>と書いてあったら,その先</TABLE>というタグがくるまで,その中はテーブルだ,<B>と書いてあったら,そこから</B>までは太字だ,とHTMLを解釈するソフトウェアです。パーサーがあるおかげでブラウザはHTMLを正しく表示できているのです。

HTMLのパーサーがHTMLを解釈するのに対して,XMLパーサーはXMLをXML の文法にのっとってXMLを解釈します。XMLの文法はHTMLに似ているところが多いですが,パーサーによって異なる解釈が生じないように,HTMLよりも厳しく規則が決まっています(IEとNetscapeで同じHTMLでも表示が異なることがあるのはご存知かと思います)。たとえば,XMLでは必ず開始タグと終了タグは1対1 で対応していなくてはならなかったり,入れ子の順番が正しく並んでいる必要があります。

HTMLでは

<P><B>hogehoge</P></B>

は受けつけられますが,XMLでは

<P><B>hogehoge</B></P>

としなくてはなりません。

※<P>に対する終了タグ</P>の場所が違いますね

インターフェースとDOM

次にインターフェイスです。インターフェイスとは,プログラムを書く際に使用する関数を呼び出すための,その呼び出し方の決まりです。XMLでは通常オブジェクト指向の言語(C++やJavaなど)を使用しますので,実際には関数はメソッドやメンバ関数になります。よって,XMLを扱うインターフェイスを決める,ということはXMLを扱うためのメソッドの仕様を決める,すなわちXMLのエレメント(要素データ)を得るためにはこんなメソッドをこんなパラメタを渡して呼ぶ,などといったことを決めています。これがインターフェイスです。

最後にDOMです。DOMとはDocument Object Modelの略です。ここでいうDocumentとはXML文書のことで,XMLを扱うにあたって各要素をオブジェクトとして扱えるようにするのがDOMインターフェイスです。

要素をオブジェクトとして扱う,とはあまり分かりやすい表現ではないので,再度HTMLを引き合いに出して説明してみます。HTMLの表示をブラウザ上で動的に扱うDHTML(ダイナミックHTML)では各HTMLの要素にID属性をわりあてることで,そのIDを持った要素をスクリプトからオブジェクトとして扱えるようにしています。たとえば,

<div id="MyID">hogehoge</div>

というソースがあった場合に,スクリプトで

document.all.MyID.innerText ="payo"

とすることで,divタグで囲まれた文字列を"payo"に変更することができます。ここでは,divタグで囲まれた文字列をオブジェクトとして扱っているわけです。このDOMインターフェイスがW3Cによって細かに定義されているため,XMLプログラミングではさまざまなプラットフォームにおいて同様の環境でプログラムを書くことができるのです(一部に標準のDOMとは異なるインターフェイスを定義したパーサーも存在しますが)。

まとめてみると,XMLを扱うプログラミングでは,パーサーによってXML文書を解釈し,各項目(エレメントであるとか,属性といったもの)をオブジェクトとして扱うためのDOMインターフェイスを利用してXML文書を操作する,ということになります。

実際のプログラミング~ XMLからCSVを作る

最後に実際のプログラミング例を示します。内容的にいきなり難しくなってしまいますが,さっと読んでみてなんとなく感じをつかんでもらえればいいと思います(環境があれば,入力して動かしてみるとなおよいと思いますが)。XMLパーサーにはインフォテリアのiPEX Developer Editionを使用しています。iPEXはMSXMLとは違い商用のXMLプロセッサですが,Developer Editionとして開発に使用する目的に限り無料で利用できます(詳細はライセンスをご覧ください)。これはインフォテリアのWebページからダウンロードできます。特にプラットフォームに依存したプログラムは書いていませんが,今回は開発環境にVisualC++6.0を使用したのでWindosのLIB版をダウンロードしてください。

プログラムの流れ

今回のプログラムはリスト4のアドレス帳を表現するXMLを読み込んでデータをCSV形式(Comma Seperated Value,カンマ区切りの値でできたデータ。表計算やデータベースで扱いやすいデータ形式)で吐き出すプログラムを作成します。プログラムはパラメタに読み込むXMLを受け取り,結果を標準出力に書き出します。リスト5がそのソースです。順に説明していきましょう。

◆リスト4◆
<?xml version="1.0"encoding="Shift_JIS"?>
<!DOCTYPE addressbook [
<!ELEMENT addressbook (item *)>
<!ELEMENT item (name,age,sex,zip,address,phone)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
] >

<addressbook>
 <item>
  <name>Okuyama Hiroyki</name>
  <age>25</age>
  <sex>Male</sex>
  <zip>xxx- xxxx</zip>
  <address>1- 2- 3,dokka- shi,Tokyo</address>
  <phone>xxx- xxx- xxxx</phone>
 </item>
 <item>
  <name>Suzuki Daisuke</name>
  <age>27</age>
  <sex>Male</sex>
  <zip>yyy-yyyy</zip>
  <address>3-23-4,inaka-chou,inaka-shi,inaka</address>
 <phone>yyyyy-y-yyyy</phone>
 </item>
 <item>
  <name>Okabe Kyoko</name>
  <age>29</age>
  <sex>Female</sex>
  <zip>zzz-zzzz</zip>
  <address>409- 3,tokai- cho,Tokyo</address>
  <phone>zz-zzzz-zzzz</phone>
 </item>
</addressbook>

まず,XML文書を読み込んでDOM Treeを構築,すなわちDocumentクラスのインスタンスを作ります(DOMが木の枝のような構造を決める)。iPEXではXML の読み込みにはInputStreamから派生したクラスとXMLReaderを使用します。InputStreamから派生したクラスには,InputStreamByFile,InputStreamByURL,InputStreamByString,InputStreamByByteがあり,それぞれ,ファイルから,URLから(http,ftp,fileのスキームをサポート),メモリ上の(ワイドキャラ)文字列,そしてメモリ上のバイト列からの読み込みを可能にしています。今回はリスト4のファイルを用意してInputStreamByFileで,ファイルからXML文書を読み込んでみます。リスト5のreadXMLFromFile(44~57行)がそのソースです。

InputStreamByFileのインスタンスを作成し,そのStreamからXMLReaderがreadメソッドで読み込みます(52行)。readメソッドでは,Documentへのポインタを引数として渡し,XML文書の読み込み先とします。DocumentクラスはXML文書そのものを表すクラスです。このときのDocumentは,エレメントを1つも持たない、いうなれば空のDocumentとし,そのルートにXML文書を読み込みます。

空のDocumentを用意するには,iPEXではIPEXDocumentクラスのcreateDocumentObjectスタティックメソッドを使用します(50行)。実は,DOMの仕様では空のDocumentを作るメソッドは用意されていないため,ここは実装依存(XMLパーサーによる)ということになります。XMLReaderのコンストラクタで,NORMALIZEとVALIDATEを指定していますが,これらはXML文書を読み込むときのオプションになり,それぞれXML文書の正規化,DTDによるバリデーションを指定します。正規化とは,文字参照(直接入力できない文字を10進数もしくは16進数で表記したもの)や実体参照(XML内部で使用できない,たとえば"<"のような文字を&lt;として表すように,文字列を置換するためのもの-一般実体-,または外部のファイルを指すもの-パラメタ実体)の展開,連続する空白文字を1つの空白文字にする,といったことを行うことをいいます。DTDによるバリデーションとは,XMLの構造を記述したDTD(リスト4の"<!DOCTYPE"から"] >"まで)にXMLが正しく沿っているかどうかをチェックすることです。

◆リスト5◆

01 :// xml2csv.cpp :コンソールアプリケーション用のエントリポイントの定義
02 ://
03 :
04 :#include <iostream>
05 :#include "ipexdom.h"
06 :
07 :#define WSTR(str)iPEX::Character::toWCString(str,L"shift_jis")
08 :#define MBSTR(str)iPEX::Character::toMBString(str,L"shift_jis")
09 :
10 :using namespace std;
11 :using namespace iPEX::DOM;
12 :using namespace iPEX::Stream;
13 :
14 :

15 :IPEXWSTRCLASS xml2csv (const Document*pDoc)throw (DOMException){
16 :if (!pDoc)
17 :return IPEXWSTRCLASS();
18 :
19 :Element*pElem =pDoc- >getDocumentElement();
20 :IPEXWSTRCLASS str =L"name,age,sex,zip,address,phone\ n";
21 :int i =0;
22 :Node*pItemNode =pElem- >getChild (0,L"item");
23 :while (pItemNode =pElem- >getChild (i,L"item")){
24 :str +=L"\ "";
25 :str +=static_cast<Element*>(
26 :pItemNode- >getChild (0,L"name"))- >getText();str +=L"\ ",\ "";
27 :str +=static_cast<Element*>(
28 :pItemNode- >getChild (0,L"age"))- >getText();str +=L"\ ",\ "";
29 :str +=static_cast<Element*>(
30 :pItemNode- >getChild (0,L"sex"))- >getText();str +=L"\ ",\ "";
31 :str +=static_cast<Element*>(
32 :pItemNode- >getChild (0,L"zip"))- >getText();str +=L"\ ",\ "";
33 :str +=static_cast<Element*>(
34 :pItemNode- >getChild (0,L"address"))- >getText();str +=L"\ ",\ "";
35 :str +=static_cast<Element*>(
36 :pItemNode- >getChild (0,L"phone"))- >getText();str +=L"\ "\ n";
37 :
38 :i++;
39 :}
40 :
41 :return str;
42 :}


43 :

ドキュメントからデータを取り出してCSV形式に書き出す
44 :Document*readXMLFromFile (const IPEXWSTRCLASS&fileName){
45 :InputStreamByFile isbf(fileName);
46 :if (!isbf){
47 :return NULL;
48 :}
49 :
50 :auto_ptr<Document>pDoc(IPEXDocument::createDocumentObject());
51 :XMLReader reader (&isbf,Reader::NORMALIZE| Reader::VALIDATE);
52 :if (!reader.read(pDoc.get())){
53 :return NULL;
54 :}
XML文書の
読みこみ
55 :
56 :return pDoc.release();
57 :}
58 :
59 :int main(int argc,char*argv[ ] )
60 :{
61 :try {
62 :
63 :ShiftJISConverterFactory _fSJIS;
64 :
65 :if (argc <2){
66 :cout <<"usage :readXML <xml document>"<<endl;
67 :return - 1;
68 :}
69 :
70 :Document*pDoc =readXMLFromFile (WSTR(argv[ 1] ));
71 :
72 :cout <<MBSTR(xml2csv (pDoc))<<endl;
73 :return 0;
74 :}
75 :catch (DOMException&e){
76 :cout <<"caught Exception code="<<e.getCode ()<<endl;
77 :return - 1;
78 :}
79 :}
80 :
 

また,今回の例ではXML文書のエンコーディング指定がShift JISなので,Shift JIS Converter Factoryのインスタンスを作る必要があります(63行)。実際には何のエンコーディングが指定されたXML文書を読み込むかわからないので,すべてのConverter Factoryを作ることになるでしょう。

DOM Treeを構築することができたので,今度はDocumentからデータを取り出してCSV形式で書き出します(15~42行)。今回は,XML文書を読み込むときにValidation(妥当性の検証)をかけているのこともあるので,細かなエラー処理は省いています。

まず,getDocumentElementでルートエレメントを取り出し(19行),その後,ルートエレメントの子であるitemエレメントを順番にひとつずつ処理しています(21~39行)。子供のエレメントを取り出すには,いくつかのメソッドがあります。今回は,getChildで特定のエレメントだけを取り出しています。ほかのメソッドとして, getFirstChild/getNextSiblingの組み合わせですべての子エレメントを取り出し,ひとつずつitemエレメントであるか調べる方法があります。

ここでひとつ注意しなくてはならないことがあります。リスト4のXMLを見ると,addressbookエレメントの直下のノ一ドにitemエレメントが入っているように思えるのですが,実は,addressbookの1番目の子ノードはitemエレメントではありません。ここには,空白文字列をもったテキストノードが入っています。XMLでは,すべての空白文字を保持するように仕様で決められているため,どのXMLプロセッサを使用しても,そのようになるはずです。話が少し前後しますが,iPEXでは,XMLReaderでNORMALIZE_WHITESPACEオプションを指定することで,空白文字だけのテキストノードを削除することができます。

itemエレメントを取り出したら,あとはそれぞれに対して,各要素の内容を順番に文字列に入れていくだけです。要素を取り出すには,DOMの標準にしたがって行う場合には,各エレメントの子ノードをひとつずつ調べて,テキストノードを探す必要があるのですが(ちょうどitemエレメントを探し出したように),今回はi PEXで用意された,getTextメソッドを使用します。これを使うと,エレメントの最初のテキストノードの内容を返してくれます。

以上のようにして,XMLからデータを取り出し,CSVとして出力することができました。

おわりに

かなり駆け足で説明してきたので,説明の足りないところなどがあると思います。とくに今回はネームスペースまわりなど,DOM Level2で導入されたインターフェイスには触れていませんので,機会がありましたら,一度DOMの仕様を読まれることをお勧めします(http://www.w3.org/DOM)。最初から読むのは難しいでしょうから,仕様のドキュメントのAppendixとして,インターフェイスの定義がありますので,ちょっとここを眺めてどんなメソッドがあるのか見てみるだけでもいいかもしれません。

図:W3CのDOMの仕様ページのイメージ

http://www.w3.org/DOM

© 2001 Infoteria Corporation 【禁無断転載複製引用】

このページのトップ