目次


ヒント: コマンド・ラインXML処理

シェルからXML文書を処理する

Comments

あまり言いたくはありませんが、XMLツールは、UNIXライクなコマンド・ラインで利用できるテキスト・ユーティリティーの便利さのレベルには、まだ到達していません。空白やコンマで区切られた行単位のテキスト・ファイルに対して、sedgrepxargswccut、パイプ、および短いシェル・スクリプトを巧みに組み合わせるだけでどれほどのことが実行できるかには、実に驚かされます。

私の意見では、フラットなテキスト・ファイルに対して行なうことが可能なモジュール方式の処理を、XMLが本質的に受け付けないわけはないと思います。XML開発者に必要なのは、複数のXMLツールを協働させる最善の方法を、経験を通して学ぶことです。たとえば、このヒントを執筆する際に、いくつかの現実的なサンプル・タスクを私は念頭に置きました。しかし、試してみて分かったことは、コマンド・ライン機能を備えたツールでさえ、スムーズに共同作業する術をまだ身に着けていないということです。もっとも、複数のツールを使用して作業することが全く手に負えないわけではなく、若干のラップ処理が必要になるだけです。

注目するべき1つの事実は、かなり多くの人々が類似したシンプルなツールの様々なバージョンを、様々なプログラミング言語で作成してきたということです。それぞれのバージョンの動作は少しずつ異なりますが、全体的に言えば同じタスクを行なっているという傾向があります。このヒントでは、xml_indexerxmlcat、およびxpath というツールを調べます。最初の2つは、私のGnosis Utilitiesから取ったものです。残りの1つは、Matt Sergeant氏が作成したPerlモジュールで、CPANから入手しました。

XML形式の散文から単語を検索する

以前の記事の中で、私は、xml_indexer について書きました (参考文献を参照)。これは、XML文書内の語句の索引を、語句のXPathによって作成するツールです。たとえば、次のようにして、まずXML文書の索引を作成し、次に検索を実行できます。

リスト1. XPathによる索引作成と検索
% xml_indexer chap.xml
	% indexer events were
	/Users/dqm/chap.xml::/chapter/sect1[2]/sect2[1]/para[1]
	/Users/dqm/chap.xml::/chapter/sect1[2]/sect2[4]/para[3]
	1 files matched wordlist: ['events', 'mostly']
	Processed in 0.062 seconds (SlicedZPickleIndexer)

これらのコマンドにより、XML文書chap.xml内の要素で、"events" と "were" という単語が含まれている (必ずしもこの順序どおりではなく、近接しているとも限らない) ものが表示されます。もし他のXML文書を索引に追加したとすると、それらの文書内でマッチする検索結果も表示されることになります。ちなみに、複数の文書について索引を作成したとしても、新しい検索はほとんど瞬時に実行されます。

特定の文書内の特定のXPathの位置に単語が出現することが分かることにも多少の意味がありますが、検索の主眼は通常、マッチした実際の内容を見ること (あるいは、その内容をさらに処理すること) です。そのためには、コマンド・ラインxpath ツールを利用する必要があります。私は、PerlのXML::XPath を導入しました (このツールの動作を気に入っているので)。

見つかったXPathをxpath ツールに対してカット・アンド・ペーストすることが可能です。たとえば、次のようにします。

リスト2. 手作業でXPathを調べる
% xpath chap.xml '/chapter/sect1[2]/sect2[4]/para[3]'
	Found 1 nodes:
	-- NODE --
	<para>It is not particularly remarkable that...
	...
	</para>

これは、これらのツールに立派なモジュール性があることを示しています。さらに、xpath に引き渡すXPathにワイルドカードが含まれているとすると、複数のノードにマッチする可能性もあります。残念ながら、indexer の出力は、xpath にパイピングするのに適した形式とは言えません。マッチした単語を含むノードを自動的に表示するためには、indexer の出力にある "::" の位置でファイル名とXPathを分離し、xpath で一度に1つのXPathだけを表示する必要があります。それよりも、もっと良い方法があります。

第1の小さなシェル・スクリプト

読者の皆さんなら、xargsapply、パイプ、その他を巧みに組み合わせて、indexerxpath との間のインピーダンス不整合に対処する方法を見つけことができるでしょう。しかし、私としては、短い (そして再利用可能な) シェル・スクリプトを記述するほうが簡単だと分かりました。

リスト3. find-xml-elements
#!/bin/sh
	for hit in `indexer $@ 2> /dev/null`
	do
	  echo $hit | sed 's/::/ /' > loc.tmp
	  cat loc.tmp | xargs xpath 2> /dev/null
	  echo
	done
	rm loc.tmp

上手に設計された他のコマンド・ライン・ツールと同様に、indexer およびxpath は、情報メッセージをSTDERR (標準エラー出力) に送り、実際の結果をSTDOUT (標準出力) に送ります。上記のスクリプトでは、STDERRのメッセージを考慮に入れていません。これで、次のように打ち込むだけで、指定した単語が出現するすべてのノードを簡単に表示できるようになりました。

リスト4. XML要素から単語を検索する
% find-xml-elements events were
	<para>Lest we forget some events in a recent decade...
	...
	Salem and by HUAC.</para>
	<para>It is not particularly remarkable that...
	...
	being uncovered.</para>

とりあえず、ここまでは順調です。検索スクリプトからは一連のXML断片が出力され、それぞれのXML断片の最上位レベル要素に、検索された単語が含まれています。ただし、その結果は一般に、整形式のXML文書ではありません。複数のルート要素があるからです。

XML文書の比較とテキストの抽出

XMLデータを分析する際に挑戦となることの1つは、XML文書に含まれる内容には、セマンティック (意味) には無関係の、フォーマット上の差異があり得るという点です。つまり、パーサーによる構文解析中に、一部の空白は無視されることがあり、属性の順序はなくなります。空要素は、自己完結型のこともあれば、終了タグがある場合もあります。実体は、いくつかの異なる方法でエンコードされます。実のところ、パーサーの観点からは無視できない空白の多くでさえ、アプリケーションの観点からすると全く無意味なことがあります。また、改行やインデントでフォーマットを整えると人間にとって読みやすくなるので、多くのアプリケーションはそのようなスタイル上のフォーマット設定を (オプションとして) 実行します。

XML文書をセマンティック的に有用な方法で比較するツールが、かなりたくさん作成されてきました。その大部分には、xmldiff といった自明の名前が付けられています (Googleでその名前を検索すると、様々なプログラミング言語で作成されたツールが見つかります)。XML文書のそのような比較の基礎になっているのは、各文書のレイアウトの正規化 (canonicalization) です。XML文書の正確なレンダリングに関して、一貫したアルゴリズム上の判断をいったん下した後であれば、セマンティック的に類似した文書の比較は、diff のような汎用ツールを使用して簡単に実行できます。

この記事の例では、私が作成したxmlcat というPythonスクリプトを使用します。このツールは複雑なものではありません。標準のcat ユーティリティーとよく似た働きをしますが、途中でXML文書を正規化します。xmlpp (参考文献を参照) などの類似ツールよりも、xmlcat を私が好む理由は、Webブラウザーのlynx からヒントを得たオプションが追加されていることです。xmlcat--dump 引数を渡すと、XML文書からタグを除去して、テキスト内容だけが出力されます (垂直方向に空白を入れることは、これを行なうためのなかなかうまい方法です)。この機能は、データ指向のXMLではほとんど使い道がありませんが、マークアップされた散文では便利です。

テキストを表示する第2のスクリプト

散文の入ったXML文書から単語を検索する場合は、マークアップよりも内容の方に関心があることがほとんどでしょう。xmlcat --dump を使用してフィルター処理する方法は、不必要なXMLタグを除去するのに打って付けの手段です。しかし、find-xml-elements の出力をxmlcat に直接パイピングするのは間違いです。find-xml-elements の出力は整形式のXML文書になっていないからです (前述のとおり、複数のXML断片になっています)。この問題は、次のような短いシェル・スクリプトによって解決できます。

リスト5. find-xml-text
#!/bin/sh
	for hit in `indexer $@ 2> /dev/null`
	do
	  echo $hit | sed 's/::/ /' > loc.tmp
	  cat loc.tmp | xargs xpath 2> /dev/null | xmlcat --dump
	  echo
	done
	rm loc.tmp

find-xml-text からの出力は、標準的なテキスト・ユーティリティーと共同作業するのに適しています。たとえば、特定の検索語を含む段落すべてを表示したい場合に、各行の左側にあるインデントを除去し、行長を制限するには、次のようにします。

リスト6. XML要素のテキストから単語を検索する
% find-xml-text events were | sed 's/^ *//' | fmt -w 70
	Lest we forget some events in a recent decade...
	...
	...those in Salem and by HUAC.
	It is not particularly remarkable...
	...
	...being uncovered.

ダウンロード可能なリソース


関連トピック

  • Kip Hampton氏の昨年の価値ある記事「Perl and XML on the Command Line」をお読みください。コマンド・ラインXML処理のためのPerlツールが紹介されています。
  • Perlのツールであるxmldiff (XML文書の比較) およびxmlpp (XML pretty printer、XML整形印刷ツール) は、DecisionSoftのサイトにあります。
  • Gnosis Utilitiesのサイトにアクセスして、この記事で紹介されているいくつかのユーティリティーをダウンロードしてください。
  • 以前のXMLの論考の記事を読み直してください。XML文書の全文索引をXPathによって作成する方法について説明しています (developerWorks、2001年10月)。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=242479
ArticleTitle=ヒント: コマンド・ラインXML処理
publish-date=05072003