レベル: 初級 Uche Ogbuji (uche@ogbuji.net), Consultant, Fourthought, Inc.
2002年 9月 01日 変換を複数のフェーズ (段階) あるいはパス (pass) に分けて実行すると、より簡潔明瞭になります。まず何らかの中間出力が生成された後、それが最終的な出力形式に変換されるのです。中間形式は1つだけとは限りません。このヒント記事では、一般的なノード・セット拡張機能を利用してXSLT操作を2つまたはそれ以上の変換フェーズに分割する方法について、Uche Ogbuji氏が説明します。
読者がUNIXユーザーであれば、パイプ の概念をご存じでしょう。これは、1つのプログラムの出力を別のプログラムの入力にするメカニズムです。パイプはおそらく、疎結合されたコードをモジュラー化する初期の事例の主な基礎になっていたと思われます。それぞれのUNIXコマンドは非常にシンプルで目標がはっきりしています。複雑なアクションは、そのようなコマンドを複数組み合わせることによって生み出されます。XSLTを使ったXMLの処理もまた、これと同様のモジュラー化によって大きく効率化されます。
変換を複数のフェーズあるいはパス (pass) からなるセットに分割すれば、コードをより簡潔にできるとともに、再利用も促進されます。残念ながら、純粋なXSLT 1.0では、変換の入力を処理するほとんどのコマンドは出力を扱うことができません。この制限はXSLT 2.0では取り除かれました。しかし、(これから何年も普及するであろう) XSLT 1.0でさえ、通常XSLTベンダーによって提供される拡張機能を利用すれば、こうした制限を取り除くことができます。
このヒントを読み進めるには、XSLTに関する知識が必要です。
テーブルを分解する
これから例示する小さなXSLTテンプレートは、文書テーブル (表) を入力として、各行の最初の項目だけを表示します。このテンプレートは、(SGMLで普及しているテーブル・モデルに基づく) DocBookのようなテーブルを処理するよう意図されています。サンプル・テーブル (db-table.xml) がリスト1 に示されています。
リスト1. DocBook形式の簡単なテーブル (db-table.xml)
<table frame="all">
<title>Numbers and tongues (数と言語)</title>
<tgroup cols="3" align="left" colsep="1" rowsep="1">
<thead>
<row>
<entry>1</entry>
<entry>2</entry>
<entry>3</entry>
</row>
</thead>
<tfoot>
<row>
<entry>I</entry>
<entry>II</entry>
<entry>III</entry>
</row>
</tfoot>
<tbody>
<row>
<entry>one</entry>
<entry>two</entry>
<entry>three</entry>
</row>
<row>
<entry>uno</entry>
<entry>dos</entry>
<entry>tres</entry>
</row>
<row>
<entry>otu</entry>
<entry>abuo</entry>
<entry>ato</entry>
</row>
</tbody>
</tgroup>
</table>
|
リスト2 (db-onecol.xslt) は、テーブルの第1列だけを表示させる変換です。
リスト2. DocBook形式のテーブル (db-onecol.xslt) の第1列だけを表示させるXSLT変換
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0"
>
<xsl:output method="text"/>
<xsl:template match="table">
<xsl:value-of select="title"/><xsl:text> </xsl:text>
<xsl:for-each select="tgroup/thead/row">
<xsl:value-of select="entry[1]"/><xsl:text> </xsl:text>
</xsl:for-each>
<xsl:for-each select="tgroup/tbody/row">
<xsl:value-of select="entry[1]"/><xsl:text> </xsl:text>
</xsl:for-each>
<xsl:for-each select="tgroup/tfoot/row">
<xsl:value-of select="entry[1]"/><xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:transform>
|
これによる出力は、単純なテキストです。エンティティー は、テキストが空白文字としてスタイルシートから除去されないようにするために、xsl:text に挿入される改行です。残りの部分は単純です。table要素が検出されれば、titleが出力となり、続いてテーブル・ヘッド (thead)、本文 (tbody)、フッター (tfoot) の各行の最初の項目だけが出力されます。ここでは、tgroup/*/row を使用したただ1つのxsl:for-each という単純な実装にはしませんでした。文書内ではthead、tbody、tfoot の各要素が任意の順番で現れる可能性がありますが、私はこれらを特定の順序で処理させたかったからです。以下のセッションは、この変換が実行される様子を示しています。
$ 4xslt db-table.xml db-onecol.xslt
Numbers and tongues
1
one
uno
otu
I
|
テーブル・モデルのミスマッチ
さて、リスト3 (xhtml-table.xml) のようなXHTML形式のテーブルを同じように処理してみましょう。
リスト3. XHTML形式のテーブル (xhtml-table.xml)
<table border="1" frame="box">
<caption>Numbers and tongues</caption>
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody>
<tr>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
<tr>
<td>uno</td>
<td>dos</td>
<td>tres</td>
</tr>
<tr>
<td>otu</td>
<td>abuo</td>
<td>ato</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>I</td>
<td>II</td>
<td>III</td>
</tr>
</tfoot>
</table>
|
このテーブルは要素名が異なり、編成も少し異なっているので、DocBookテーブル・テンプレートを単純に再使用することはできません。もちろん、このテンプレートをコピーして少しばかり修正を加えれば、XHTML要素用の特別なバージョンを作成できます。しかし、これはあまりモジュラー的な方法ではありません。別の方法は、XHTMLをDocBook形式に変換して、それをDocBookテンプレートにかけることです。こうすることの利点は、いったん変換が完成すれば、DocBookテーブル用の他の機能を再利用できることです。
リスト4 (xhtml-onecol.xslt) は、DocBookテーブル・モジュールでXHTMLテーブルを扱えるようにするための変換です。
Listing 4. XHTML形式のテーブル (xhtml-onecol.xslt) の第1列だけを表示させるXSLT変換
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0"
>
<xsl:import href="db-onecol.xslt"/>
<xsl:template match="/">
<xsl:apply-templates mode="xhtml"/>
</xsl:template>
<xsl:template match="table" mode="xhtml">
<xsl:variable name="db-table">
<xsl:call-template name="xhtml-table-to-db"/>
</xsl:variable>
<xsl:apply-templates
select="exslt:node-set($db-table)/table"/>
</xsl:template>
<xsl:template name="xhtml-table-to-db">
<xsl:copy>
<title><xsl:value-of select="caption"/></title>
<tgroup cols="{count(thead/tr/th)}">
<thead>
<row>
<xsl:for-each select="thead/tr/th">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</thead>
<tfoot>
<row>
<xsl:for-each select="tfoot/tr/td">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</tfoot>
<tbody>
<xsl:for-each select="tbody/tr">
<row>
<xsl:for-each select="td">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</xsl:for-each>
</tbody>
</tgroup>
</xsl:copy>
</xsl:template>
</xsl:transform>
|
ここで重要なことが1つあります。要点を目立たせるために、私はこれらの例を意図的に単純化しました。これらのスタイルシートは、(テンプレートやモードを多用する)プッシュ ・スタイルのXSLTではなく、(xsl:for-each およびxsl:value-of を多用する)プル ・スタイルのXSLTを使用しています。このようにした理由は、プッシュ・スタイルの方が多くの点で優れているにもかかわらず、プル・スタイルの方がより普及しているからです。たとえば、実際のプロジェクトでは、自分自身の変換のバリエーション (変種) としてXHTMLをDocBookに変換するためのテンプレートを書くでしょう。さらに、XHTMLおよびDocBookテーブルのさまざまな一般的ケースを扱うには、より多くの論理をテンプレートに含める必要があるでしょう。
マルチパス技法の最も重要な点は、以下の行です。
<xsl:apply-templates select="exslt:node-set($db-table)/table"/>
|
これは、1つのフェーズから次のフェーズへのハンドオフ (引き継ぎ) です。最初のパスでは、変数db-table 内でXHTMLテーブルがDocBook形式に変換されます。この結果として生成される出力のツリー断片は、リスト1 に非常に似ています。これを2番目のパスの入力として扱うには、結果ツリー断片からノード・セットに変換する必要があります。exslt:node-set は、まさにこの機能を実行します。この拡張機能はいくつかのプロセッサーでサポートされています。EXSLT拡張機能をサポートしないプロセッサーでさえも、これと同様に機能する独自のノード・セット機能が提供されている場合がほとんどです。
この新しいノード・セットからテーブル要素を選択して、2番目のパスで利用します。2番目のパスでは、インポートされたdb-onecol.xslt モジュールからのテーブル・テンプレートが処理を行います。ここでは、モード (xhtml) を使ってXHTMLテーブルを選択します。こうすれば、このテンプレートは、マッチングが同等でインポート優先順位のより低いDocBookテンプレートの操作を妨害しません。
この変換の出力は、純粋なDocBookテーブルの変換と同じです。私のまったく意図したとおりに、DocBookコードを再利用することができました。
要約
今回の例は、実際のプロジェクトで私が遭遇した状況を極端に単純化したものです。XHTMLソース処理のために多数のDocBook処理テンプレートを再利用する必要がありました。第1のパスでXHTMLの内容をDocBookに変換し、後続のパスで標準的なDocBookテンプレートを再利用することによって、私は多くの作業とデバッグを節約することができました。マルチパスXSLTの概念は、これよりも一般的です。コードの再利用が促進されるのに加えて、複雑な変換処理をわかりやすい断片に分けることができます。読者がこの次に煩雑なXSLTの問題にぶつかったら、それを複数のパイプからなる処理として単純化あるいはモジュラー化できないかどうか、ぜひ検討してください。
参考文献
著者について  | 
|  | Uche Ogbuji氏は、Fourthought, Inc. のコンサルタント兼共同設立者です。この会社は、企業のナレッジ・マネジメントのためのXMLソリューションを専門とするソフトウェア・ベンダー兼コンサルタント会社です。Fourthoughtでは、XML、RDF、およびナレッジ・マネジメント・アプリケーション用のオープン・ソース・プラットフォームである4Suiteを開発しています。Ogbuji氏は、ナイジェリア出身のコンピューター・エンジニア兼ライターで、現在は、米国コロラド州ボールダーに住み、そこで働いています。Ogbuji氏の連絡先はuche.ogbuji@fourthought.com です。 |
記事の評価
|