XSLT 2.0 を使用してコンテンツに構造とセマンティクスを追加する

構造化されていない、文章によるコンテンツを構造化された機能豊富な XML に変換する

Comments

従来から、文章で構成されたコンテンツには、コンテンツの実際の構造や意味を伝えるためではなく、コンテンツの表示方法 (フォント・サイズやテキストの配置など) を伝えるために、マークアップが使われています。このやり方は、本やオフィス文書、そして多くの Web ページに当てはまります。文章で構成されるこれらのコンテンツには、一切マークアップが使われていないものや、プレーン・テキストの段落としてレンダリングされるだけのものもあります。

コンテンツ管理、電子出版、そして高度な検索およびクエリー技術の出現により、今やコンテンツの所有者は構造化された情報の力を実感し、新しく作成するコンテンツにマークアップを適用するようになっています。しかし、多くの所有者は、未だに構造化されていないコンテンツや半構造化コンテンツ、または表示目的でしかマークアップされていないコンテンツを持て余しています。

既存のコンテンツの有用性を大幅に高めるには、構造を追加してください。それによって、以下のことが可能になります。

  • スマート・フォン、電子書籍リーダー、および代替 Web ブラウザーなどといった出力デバイスごとに、コンテンツのスタイルを設定する。
  • 特定の種類のテキスト (文書内または文書間の参照用ハイパーリンクや、住所のポップアップ道順案内など) に関連付けられたインタラクティブな振る舞いを実装する。
  • コンテンツの代替表示 (目次、索引、要約表示など) を生成する。
  • コンテンツのフィールドを基準に、より絞り込んだ検索を行えるようにする。
  • コンテンツ全体で、より一貫したフォーマットを確実にする。
  • コンテンツの検証を強化する (法律文書の文書内参照が有効であるかどうかを判別するなど)。

XSLT 2.0 を使用する

XSLT 2.0 は、文章で構成されたコンテンツに構造を追加するのに非常に適した技術です。文章で構成されるコンテンツは、XML または XML に簡単に変換できる形式 (HTML など) で表現されることが多くなってきていますが、テキストの操作によく使われているスクリプト言語とは異なり、XSLT は完全に XML に対応した言語であり、XML の構文、エンコーディング、名前空間のさまざまなバリエーションを認識します。

XSLT が文章で構成されたコンテンツに適している理由には、その柔軟性も挙げられます。XSLT では入力文書のさまざまなイベントをテンプレート・ルールに従って処理するため、順次プロセスによって厳格に変換を制御するのではなく、コンテンツに応じた変換を行うことが可能となっています。

XML のスタイル設定に重点を置いていた XSLT 1.0 とは異なり、高度なコンテンツ変換機能を備えた XSLT 2.0 では、以下の操作に対処することができます。

  • 正規表現によってテキストのパターンを識別する。
  • 値または位置を基準に要素をグループ化する。
  • 1 つのスタイルシートで 1 つの文書に複数のパスを作成する。
  • 1 つの文書を複数の文書に分割し、複数の文書を 1 つの文書に結合する。

XSLT を使用するための最初のステップは、コンテンツを XML にすることです。文書エディターやその他のコンテンツ・ツールのほとんどには、XML へのエクスポート機能が備わっています。使用するツールによっては XML が極めて複雑になる可能性があるため、この最初のステップでは、XML を操作しやすい単純な形に変換する必要があります。

HTML から変換する場合には、HTML Tidy (「参考文献」にリンクを記載) が HTML を XHTML に変換してくれます。XHTML は整形式 XML であるため、XSLT の入力として使用できるだけでなく、文書を単純化します。

手法

ここからは、XSLT 2.0 を使って構造とセマンティクスをコンテンツに追加するための手法を説明します。入力文書として特に重点を置いているのは XHTML と Microsoft Office Word XML ですが、ここで説明する手法は、文章で構成されるあらゆる入力文書に適用することができます。

このセクションに記載するすべてのサンプル・コードは、ダウンロード可能な ZIP ファイルとして用意されています (「ダウンロード」を参照)。XSLT 2.0 プロセッサーとしてお勧めするのは、Saxon です (「参考文献」にリンクを記載)。

テキストのパターンを認識する

多くの場合、テキストが従う特定のパターンを認識して、そのパターンをマークアップすると役に立ちます。URL、E メール・アドレス、電話番号、文書内参照、そしてウィキ・タイプのフォーマットはすべて、正規表現を使って識別可能な共通パターンに従っています。

リスト 1 に、いくつかの E メール・アドレスがプレーン・テキストで含まれる入力文書を記載します。ここで、それぞれの E メール・アドレスへのリンクを有効にするために、a (アンカー) 要素を使って E メール・アドレスをマークアップしたいとします。HTML では、ユーザーがアドレスをクリックすると、そのアドレスに E メールを送信できるようにします。

リスト 1. テキスト・パターンを認識する XSLT への入力例
<document>
  <p>Priscilla Walmsley can be reached at pwalmsley@datypic.com.</p>
  <p>Questions about XSLT in general are best asked on the XSL list at
    xsl-list@lists.mulberrytech.com (subscription required.) </p>
</document>

この目標を達成するのが、リスト 2 の XSLT です。ここで使用されている XSLT 2.0 の analyze-string 命令は、文字列が正規表現とマッチするかどうかをテストします。analyze-string 命令には 2 つの子要素があります。1 つは、パターンとマッチするサブ文字列の処理方法を指定する matching-substring、そしてもう 1 つは、テキストの残りの部分の処理方法を指定する non-matching-substring です。

この例では、最初のテンプレート・ルールが入力文書に含まれるすべてのテキスト・ノードを突き合わせ、regex 属性に指定された正規表現とマッチするテキストを検出するたびに、a 要素を挿入します。この要素には href 属性を指定して、文字列 mailto: を E メール・アドレスに連結します。その上で、E メール・アドレスを a 要素のなかに含めます。matching-substring 命令のなかで、パターンとマッチした文字列を表すのは、ピリオド (.) です。

パターンとマッチしないすべてのテキスト・ノードまたはその一部は、non-matching-substring 命令に指定されているように、そのままコピーされます。この XSLT における 2 番目のテンプレート・ルールは、プロセッサーに対する指示として、すべての要素をコピーし、続いてそれらの子要素をコピーするように指示しています。したがって、E メール・アドレス以外のコンテンツは変更されません。

リスト 2. テキスト・パターンを認識する XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="text()">
  <xsl:analyze-string select="."
                      regex="[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\.[A-Za-z]{{2,4}}">
    <xsl:matching-substring>
      <a href="mailto:{.}">
        <xsl:value-of select="."/>
      </a>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:copy/>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:template>

<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

リスト 3 に、上記の XSLT によって E メール・アドレスが a 要素としてマークアップされた出力結果を記載します。

リスト 3. テキスト・パターンを認識する XSLT の出力例
<document>
 <p>Priscilla Walmsley can be reached at
   <a href="mailto:pwalmsley@datypic.com">pwalmsley@datypic.com</a>.</p>
 <p>Questions about XSLT in general are best asked on the XSL list at
   <a href="mailto:xsl-list@lists.mulberrytech.com">xsl-list@lists.mulberrytech.com</a>
   (subscription required.) </p>
</document>

構造コンテナーを追加する

上位レベルでは、文書の区分やセクションを表す構造を追加すると役立つことがよくあります。例えば、DocBook といった特定の XML 語彙に従った構造を追加するなどです。あるいは、コンテンツのセクションを明確に記述することで、DITA などの技術を使ってそれらのコンテンツを簡単に再利用できるようにすることも考えられます。

リスト 4 に、フラットな構造を持つ XHTML 入力文書の一例を記載します。この文書のセクション見出し (h1h2) は段落と同じレベルにあり、セクションをグループ化する構造タグはありません。

リスト 4. セクションをグループ化する XSLT への入力例
<html>
 <h1>Chapter 1</h1>
 <h2>Section 1.1</h2>
 <p>In this section...</p>
 <p>More text</p>
 <h2>Section 1.2</h2>
 <p>In this second section...</p>
</html>

リスト 5 の XSLT は、XSLT 2.0 のグループ化機能を使用し、セクション見出しの位置に基づいて section 要素を追加します。この XSLT は、2 つのレベルのグループを作成するために、まず、属性 group-starting-with="h1" を指定した for-each-group 命令を使って、html のすべての子をグループ化します。これは、h1 要素が、このレベルにあるグループの開始を示すことを意味します。XSLT は該当するグループのそれぞれに、section level="1" 要素を挿入します。

次に、内側の for-each-group を使用して current-group 関数を呼び出し、h1 グループに含まれるすべての項目を取得して、これらの項目からサブグループを作成します。サブグループの開始は h2 要素によって特定されます。

内側の for-each-group のなかでは、XPath の current-group()[self::h2] を使用して、グループに h2 要素が含まれているかどうかをテストします。このテストの目的は、最初の h2 要素の前にある項目のすべてが含まれるグループを作成することです (この例の場合、これに該当する項目は h1 要素だけです)。これにより、h1 要素には section level="2" 要素が適用されないようにします。

リスト 5. XSLT におけるセクションのグループ化
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="html">
  <document>
    <xsl:for-each-group select="*" group-starting-with="h1">
      <section level="1">
        <xsl:for-each-group select="current-group()" group-starting-with="h2">
          <xsl:choose>
            <xsl:when test="current-group()[self::h2]">
              <section level="2">
                <xsl:apply-templates select="current-group()"/>
              </section>
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="current-group()"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each-group>
      </section>
    </xsl:for-each-group>
  </document>
</xsl:template>

<xsl:template match="h1|h2">
  <heading>
    <xsl:apply-templates/>
  </heading>
</xsl:template>

<xsl:template match="node()">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

リスト 6 に記載する XSLT の出力には、2 つのレベルの section 要素があります。

リスト 6. セクションをグループ化する XSLT の出力例
<document>
   <section level="1">
      <heading>Chapter 1</heading>
      <section level="2">
         <heading>Section 1.1</heading>
         <p>In this section...</p>
         <p>More text</p>
      </section>
      <section level="2">
         <heading>Section 1.2</heading>
         <p>In this second section...</p>
      </section>
   </section>
</document>

場合によっては、開始要素を基準にグループ化する代わりに、互いに隣接する項目をグループ化しなければならないこともあります。その一例は、リスト項目です。リスト項目は 1 つのリストのなかでグループとして表示されるのではなく、通常の段落が並んだものとして表示される場合があります。この場合に対処するには、for-each-group 命令で属性 group-starting-with を使う代わりにgroup-adjacent を使用します。

セクション番号から構造を推測する

時には、グループ化するのと同時に構造を推測しなければならないこともあります。これまでの例とは異なり、リスト 7 の入力文書のテキストは、すべて一般的な段落としてマークアップされています。この場合、構造を判断する唯一の方法となるのは、コンテンツをテストしてパターンを見つけ出すことです。

リスト 7. セクションの推測を行う XSLT へのサンプル入力
<document>
 <p>Chapter 1: In the beginning</p>
 <p>In this chapter...</p>
 <p>1.1 Introduction</p>
 <p>In this section...</p>
 <p>More text</p>
 <p>1.2 Next Steps</p>
 <p>In this second section...</p>
</document>

XSLT は、パターンを見つけ出す最も簡単な方法となります。リスト 8 の XSLT は、文書で 2 つのパスを取り、モードを使用して、この 2 つのパスの機能を区別します。

リスト 8. セクションの推測を行う XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="chapRegex" select="'^\s*Chapter\s+(\d+)\s*:\s*(.*)$'"/>
<xsl:variable name="secRegex" select="'^\s*(\d+\.\d+)\s*(.*)$'"/>

<xsl:template match="document">
  <xsl:variable name="renamed" as="element()*">
    <xsl:apply-templates select="*" mode="rename"/>
  </xsl:variable>
  <document>
    <xsl:for-each-group select="$renamed" group-starting-with="chapTitle">
      <chapter num="{replace(current-group()[self::chapTitle],$chapRegex,'$1')}">
        <xsl:for-each-group select="current-group()" group-starting-with="secTitle">
          <xsl:choose>
            <xsl:when test="current-group()[self::secTitle]">
              <section num="{replace(current-group()[self::secTitle],$secRegex,'$1')}">
                <xsl:apply-templates select="current-group()"/>
              </section>
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="current-group()"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each-group>
      </chapter>
    </xsl:for-each-group>
  </document>
</xsl:template>

<xsl:template match="p[matches(.,$chapRegex)]" mode="rename">
  <chapTitle>
	 <xsl:copy-of select="node()"/>
  </chapTitle>
</xsl:template>

<xsl:template match="p[matches(.,$secRegex)]" mode="rename">
  <secTitle>
	 <xsl:copy-of select="node()"/>
  </secTitle>
</xsl:template>

<xsl:template match="text()" priority="1">
  <xsl:choose>
    <xsl:when test="matches(.,$chapRegex) and (. is parent::chapTitle/node()[1])">
      <xsl:value-of select="replace(.,$chapRegex,'$2')"/>
    </xsl:when>
    <xsl:when test="matches(.,$secRegex) and (. is parent::secTitle/node()[1])">
      <xsl:value-of select="replace(.,$secRegex,'$2')"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy-of select="."/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="node()" mode="#all">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates mode="#current"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

最初のパスは、実際にチャプター・タイトルおよびセクション・タイトルをそれぞれ表している p 要素を判別し、その後のグループ化のパスを単純にするために、要素の名前を変更するというものです。そのためにはまず、最初のパスの結果を格納する変数 $renamed を作成します。この変数の定義内で、rename モードを設定したテンプレートを適用します。rename モードの 2 つのテンプレート・ルールは、チャプター・タイトルまたはセクション・タイトルとなっている段落を探し、それぞれの名前を変更します。チャプター・タイトルまたはセクション・タイトルになっている段落であることを判断するには、matches 関数を使用します。XSLT 2.0 で新しく追加されたこの関数は、文字列が正規表現とマッチするかどうかを判別します。この例での正規表現は、$chapRegex および $secRegex として表現されています。

リスト 9 に、最初のパスを辿った後の $renamed 変数の値が示されています。ただし、この値が結果にそのまま表示されるわけではありません。

リスト 9. セクションの推測を行う XSLT の $renamed 変数の値
<document>
 <chapTitle>Chapter 1: In the beginning</chapTitle>
 <p>In this chapter...</p>
 <secTitle>1.1 Introduction</secTitle>
 <p>In this section...</p>
 <p>More text</p>
 <secTitle>1.2 Next Steps</secTitle>
 <p>In this second section...</p>
</document>

2 つ目のパスは、for-each-group 命令で始まり、その select 属性によって、この命令が入力文書ではなく、$renamed 変数を操作することを指定します。2 つ目のパスは、前に説明した例とかなりよく似たグループ化ロジックを使用して、2 つのレベルの構造、chaptersection を作成します。

さらに、このパスはチャプター番号とセクション番号を格納する num 属性も作成します。そのために使用するのは、同じく XSLT 2.0 で新しく追加された関数、replace です。replace 関数は、3 つの引数を使用します。最初の引数は変更対象のテキスト、2 番目の引数は置換する部分と突き合わせる正規表現、3 番目の引数は置換文字列です。この例では、3 番目の引数がサブ表現を利用しています。このサブ表現の $2 に、正規表現の 2 番目の括弧とマッチしたテキストが含まれることになります。

text() テンプレート・ルールは、コンテンツからチャプター番号とセクション番号を削除します。これらの番号は、num 属性に移されているためです。この属性も同じく replace 関数を使用します。注意しなければならないのは、パターンとマッチするすべてのテキスト・ノードを置換しないようにすることです。したがって、マッチした最初のテキスト・ノードが、その親の最初のノードであるかどうかをテストしてください。

node() テンプレート・ルールは、他に特定のテンプレートを持たないすべての要素をコピーします。mode="#all" が使用されているということは、rename モードであるか、モードを使用していないかに関わらず、このテンプレートが使用されることを意味します。このテンプレート・ルール内にあるテンプレートを適用する場合には、mode="#current" を使用して、このテンプレートに最初にマッチしたモードをそのまま維持するように指定します。

モードの使用は XSLT 2.0 に始まったことではありませんが、#all および #current キーワードの使用については、文書で複数のパスを作成する機能と同様で XSLT 2.0 の新しい機能です。これらの機能は、XSLT 1.0 では結果ツリー・フラグメントの制約が原因で使用することができませんでした。

リスト 10 に、最終的な出力を記載します。

リスト 10. セクションの推測を行う XSLT の出力例
<document>
   <chapter num="1">
      <chapTitle>In the beginning</chapTitle>
      <p>In this chapter...</p>
      <section num="1.1">
         <secTitle>Introduction</secTitle>
         <p>In this section...</p>
         <p>More text</p>
      </section>
      <section num="1.2">
         <secTitle>Next Steps</secTitle>
         <p>In this second section...</p>
      </section>
   </chapter>
</document>

スタイル情報を使用する

文書の実際の構造を判断する上で重要なリソースは、名前付きスタイルです。名前付きスタイルは、通常はフォーマットを指定して標準化することを意味しますが、コンテンツの構造と意味を伝えることもできます。多くの手法では、構造とスタイルは 2 つの異なるものとして使用されています。例えば Microsoft Office Word では、テキストが段落に構造化されて、これらの段落や段落内の本文にスタイルが適用されます。HTMLの場合、構造要素は h1p などのさまざまな形になっているものの、これらの要素には、CSS スタイルシートを参照する class 属性によって、スタイルも適用されます。

図 1 に、スタイルを使用した Word 文書を示します。この文書のセクション見出しには、Heading 1 (見出し 1) および Heading 2 (見出し 2) の段落スタイルが使用されていて、インライン・テキストのフォーマットには Emphasis (強調) および Strong (より強調) の文字スタイルが使用されています。

図 1. スタイルを使用した Word 文書
段落およびインラインのテキスト・スタイルを使用した Word 文書の例
段落およびインラインのテキスト・スタイルを使用した Word 文書の例

この文書は、XSLT で実行する前に Word XML として保存しておきました。Word XML ファイルはサイズがかなり大きく、複雑なため、ここには記載しませんが、ダウンロード・ファイルのサンプルに含まれています (「ダウンロード」を参照)。

スタイルを使用する上では、スタイル・マッピング・ドキュメントを作成することをお勧めします。リスト 11 はその一例です。このスタイル・マッピング・ドキュメントは入力文書のスタイルを、結果に含まれる目的の構造要素にマッピングします。このマッピングによって、変換はより汎用的で柔軟なものになるとともに、変換を保守するのも楽になります。

リスト 11. Word を変換する XSLT で使用されているスタイル・マップ
<styles>
  <style>
    <name>BodyText</name>
    <name>Normal</name>
    <transformTo>p</transformTo>
  </style>
  <style>
    <name>ListParagraph</name>
    <transformTo>li</transformTo>
  </style>
  <style>
    <name>Heading1</name>
    <transformTo>h1</transformTo>
  </style>
  <style>
    <name>Heading2</name>
    <transformTo>h2</transformTo>
  </style>
  <style>
    <name>Emphasis</name>
    <transformTo>em</transformTo>
  </style>
  <style>
    <name>Strong</name>
    <transformTo>strong</transformTo>
  </style>
</styles>

リスト 12 の XSLT は、Word XML 文書を XML に変換して、スタイル・マップに指定された要素を作成します。この例でテンプレート・ルールを定義しなければならない要素は、w:p (段落) と w:r (本文) の 2 つだけです。この 2 つの要素のそれぞれについて、XSLT は関連付けられたスタイルを判断し、そのスタイルをスタイル・マップで検索します。そして、スタイル・マップの transformTo で検出した名前を設定した xsl:element を使って XML 要素を作成します。

リスト 12. Word スタイルから XML タグへの変換
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
        exclude-result-prefixes="w">
<xsl:variable name="styles" select="doc('stylemap.xml')/styles/style"/>

<xsl:template match="/">
  <document>
    <xsl:apply-templates select=".//w:p"/>
  </document>
</xsl:template>

<xsl:template match="w:p">
  <xsl:variable name="elName"
                select="$styles[name=current()/w:pPr/w:pStyle/@w:val]/transformTo"/>
  <xsl:choose>
    <xsl:when test="$elName != ''">
      <xsl:element name="{$elName}">
       <xsl:apply-templates select="*"/>
      </xsl:element>
    </xsl:when>
    <xsl:otherwise>
      <!-- paragraphs without a listed style are just plain p's -->
      <p>
        <xsl:apply-templates select="*"/>
      </p>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="w:r">
  <xsl:variable name="elName"
                select="$styles[name=current()/w:rPr/w:rStyle/@w:val]/transformTo"/>
  <xsl:choose>
    <xsl:when test="$elName != ''">
      <xsl:element name="{$elName}">
        <xsl:apply-templates select="*"/>
      </xsl:element>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="*"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

リスト 13 に、出力例を記載します。この例では、実際に新しい情報を追加することも、推測することもしていません。構造とスタイルを組み合わせて、1 つのタグ・セットにしているだけです。このステップまで辿り着けば、後は、この記事で説明している他の手法を適用して、構造を追加し、テキストのパターンを認識することができます。

リスト 13. Word スタイルを変換する XSLT の出力例
<document>
   <h1>Chapter 1: In the beginning</h1>
   <p>In this chapter...</p>
   <h2>1.1 Introduction</h2>
   <p>In this section there is a list.</p>
   <li>item 1</li>
   <li>item 2</li>
   <li>item 3</li>
   <h2>1.2 Next Steps</h2>
   <p>This section uses character styles
         <strong>Strong</strong> and <em>Emphasis</em>.</p>
</document>

この例では Word を使用していますが、スタイルを決定する class 属性を使用すれば、XHTML でも同じような変換を実行することができます。この変換を行う例は、おまけとして ZIP ダウンロード・ファイルに用意されています (「ダウンロード」を参照)。

フォーマット情報を使用する

場合によっては、名前付きスタイルの情報を使用できないこともあります。Word の場合、さまざまなユーザーがさまざまな手法を使用して入力文書を作成しています。それには、例えばスタイルを使用する代わりにフォントの変更を直接適用するなどの手法も含まれます。HTML でも同じように、作成者またはツールが、再利用可能な CSS クラスを定義するのではなく、個々のスタイル属性を段落やその他の要素に適用することがあります。このような場合には、フォント・サイズやフォント効果 (例えば、太字、斜体) などのテキスト・フォーマットを頼りに構造を推測するしかありません。フォーマット情報を使用して推測する構造は、スタイル情報を使用する場合よりも信頼性および一貫性に欠けますが、それでもフォーマット情報は重要なヒントになります。

図 2 に、スタイルではなく、直接フォーマットを適用した Word 文書を示します。この文書の作成者は、スタイルを使用する代わりに、全体を Normal (標準) スタイルのまま維持し、見出しとして表示する段落のフォント・サイズを直接変更して、太字と斜体を適用しています。

図 2. スタイルではなくフォーマットを使用した Word 文書
フォーマットを直接使用した Word 文書の例
フォーマットを直接使用した Word 文書の例

スト 14 の XSLT は、スタイル名ではなく、フォーマット情報に基づいて Word 文書を XML に変換します。この XSLT は、フォントのサイズと、太字または斜体であるかどうかをテストして、それが第 1 レベルの見出しであるか、それとも第 2 レベルの見出しであるかを判断します。このタイプの変換では、マッピング・ルールが複雑になるため、スタイル・マップはそれほど役に立ちません。それよりも、XSLT および XPath コードとして表現するほうが適しています。

リスト 14. テキスト・フォーマットから構造を推測する XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
        exclude-result-prefixes="w">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
  <document>
    <xsl:apply-templates select=".//w:p"/>
  </document>
</xsl:template>

<xsl:template match="w:p">
  <xsl:variable name="size" select="w:pPr/w:rPr/w:sz/@w:val"/>
  <xsl:choose>
    <xsl:when test="($size > 32) and exists(w:pPr/w:rPr/w:b)">
      <h1>
        <xsl:apply-templates select="*"/>
      </h1>
    </xsl:when>
    <xsl:when test="($size > 25) and exists(w:pPr/w:rPr/w:i)">
      <h2>
        <xsl:apply-templates select="*"/>
      </h2>
    </xsl:when>
    <xsl:otherwise>
      <p>
        <xsl:apply-templates select="*" mode="char-formatting"/>
      </p>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="w:r" mode="char-formatting">
  <xsl:choose>
    <xsl:when test="w:rPr/w:b">
      <b>
        <xsl:apply-templates select="*"/>
      </b>
    </xsl:when>
    <xsl:when test="w:rPr/w:i">
      <i>
        <xsl:apply-templates select="*"/>
      </i>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="*"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

リスト 15 に、出力例を記載します。

リスト 15. Word フォーマット変換 XSLT の出力例
<document>
   <h1>Chapter 1: In the beginning</h1>
   <p>In this chapter...</p>
   <h2>1.1 Introduction</h2>
   <p>In this section...</p>
   <h2>1.2 Next Steps</h2>
   <p>In this section there is text that uses <b>bold</b>
     and <i>italics</i>.</p>
</document>

XHTML でこのタイプの変換を行うとしたら、style 属性または fontcenter などのフォーマット要素を使用します。

注意事項とまとめ

この記事では、いずれも説明用に極めて単純な例を使用しましたが、実際に扱うコンテンツは、これらの例よりもさらに複雑になってくるはずです。シナリオによっては、かなり統一された入力文書を扱うこともあります。例えば、編集者が慎重に修正した Word 文書や、アプリケーションによって一貫して生成された XHTML 文書を変換する場合です。

その一方、コンテンツが乱雑になっていることはよくあることです。特に、コンテンツが手作業で作成されている場合には、一貫性が取れていない可能性があります。XSLT を作成するときに、コンテンツの一貫性を前提にすることはできません。そのため、コードに想定される事項を十分にテストし、未処理の例外をキャッチして警告メッセージを出力するテンプレートを作成してください。

混合されたコンテンツには、特に注意が必要です。そのようなコンテンツが含まれる文書を変換する場合には、混合コンテンツをうっかりフラット化してしまう可能性のある value-of ではなく、必ず apply-templates をふんだんに使用してください。

文章で構成されたコンテンツを処理する際には、ホワイト・スペースも重要です。XSLT で出力にインデントを付けることは避けてください。それによって、意図しないホワイト・スペースが取り込まれてしまう可能性があるからです。それとは逆に、何らかの理由があってホワイト・スペースが使用されている場合に備え、入力文書からホワイト・スペースを削除することは禁物です。

人間が出力をレビューすることは、常に推奨されます。コンテンツが大量だとレビューには時間がかかりますが、繰り返されるエラーを見つけるために、少なくとも抜き取り検査によって出力をレビューする価値はあります。


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


関連トピック

  • Introduction to XSLT」(Nicholas Chase 著、developerWorks、2007年1月): XSLT スタイルシートによって XML データの形式を変換する方法を調べてください。この記事では、XPath の基礎と、高度な XSLT 機能をいくつか抜粋して説明しています。
  • XSLT はどのような言語か」(Michael Kay 著、developerWorks、2005年4月): この記事の分析および概要で XSLT をとらえてください。XSLT がどのように生じたのか、どこが優れているのか、なぜ使うべきなのかを説明しています。
  • 連載「XSLT 1.0 から 2.0 へのアップグレード計画」(David Marston、Joanne Tong、Henry Zongaro 共著、developerWorks、2006年10月から 2007年7月): この 7 回の連載で、XSLT 1.0 での欠点に対処することを目的とした、とりわけ価値のある XSLT 2.0 の機能について詳しく学んでください。
  • developerWorks の XML エリア: DTD、スキーマ、XSLT を含め、XML 分野でのスキルを磨くための資料を見つけてください。広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM Redbooks については、XML 技術文書一覧を参照してください。
  • IBM XML 認定: XML や関連技術の IBM 認定技術者になる方法について調べてください。
  • Twitter での developerWorks: 今すぐ登録して developerWorks のツイートをフォローしてください。
  • developerWorks podcasts: ソフトウェア開発者向けの興味深いインタビューとディスカッションを聞いてください。
  • Saxon: この記事の例をテストするために使用した XSLT 2.0 プロセッサーをダウンロードしてください。
  • HTML Tidy: HTML を整形式 XML に変換するオープンソースのパーサーをダウンロードしてください。
  • IBM 製品の評価版: DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を体験するには、評価版をダウンロードするか、IBM SOA Sandbox のオンライン試用版を試してみてください。

コメント

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=749787
ArticleTitle=XSLT 2.0 を使用してコンテンツに構造とセマンティクスを追加する
publish-date=08052011