閑話休題: 平坦なXMLを構造化する

前回の応用で、xsl:for-each-groupのグルーピングの属性、group-starting-withを使って、平坦なXMLの構造化をやってみました.
あまり現実味がないと面白くないので、今回はソース文書としてWord2003の出力するWordMLを使います.Word2007、2010でもWord2003互換の出力を使えば同じ文書が作れるはずです.WordMLのソースは書式情報などが入って長すぎるので、画面のショットだけ載せておきます.必要な方は以下からダウンロードしてください.(ZIP 3KB)
 
 
イメージ 1
 
わかりやすいように赤はHeading1、青はHeading2の段落としてあります.
 
さてWordMLはそのままでは扱いづらいので、次のようなスタイルシートで簡略化します.
 
<xsl:template match="w:wordDocument">
    <doc>
        <xsl:apply-templates select="w:body"/>
    </doc>
</xsl:template>
<xsl:template match="wx:*">
    <xsl:apply-templates/>
</xsl:template>
<xsl:template match="w:p">
    <p>
        <xsl:attribute name="style" select="w:pPr/w:pStyle/@w:val"/>
        <xsl:apply-templates select="w:r"/>
    </p>
</xsl:template>
<xsl:template match="w:r">
    <xsl:apply-templates select="w:t"/>
</xsl:template>
 
これで次のような平坦なXML文書ができました.この文書は段落のテキストだけ抽出し、style属性に段落のスタイルを指定しています.style=""は、それが既定の段落スタイル(Normal)であることを表します.たまたま使っているのがWord2003の英語版なのでスタイル名が英語になっているようです.日本語だったら「見出し1」「見出し2」などとなるでしょう.
 
<?xml version="1.0" encoding="UTF-8"?>
<doc>
   <p style="Heading1">はじめに</p>
   <p style="">この文書は、平坦なXMLから構造的なXMLを作る実験データです.</p>
   <p style="Heading2">見出しレベル2</p>
   <p style="">見出しレベル2の段落です.</p>
   <p style="">まだ続きます.</p>
   <p style="">これでおしまい.</p>
   <p style="Heading2">見出しレベル2の2番目です.</p>
   <p style="">はたして構造化できるでしょうか?</p>
   <p style="Heading1">XSLTプロセッサ</p>
   <p style="">現在主に使われているプロセッサにはXSLT1.0とXSLT2.0のものがあります.</p>
   <p style="Heading2">XSLT1.0のプロセッサ</p>
   <p style="">MSXMLやXALANがあります.</p>
   <p style="Heading2">XSLT2.0のプロセッサ</p>
   <p style="">SaxonやAltovaがあります.</p>
   <p style="">これで文章はおしまいです.</p>
</doc>

さて構造化ですが、ここではスタイル属性の"Headin1"をchapterに、"Heading2"をsectionに見立てて行います.DocBookもどきの文書を作ろうという訳です.スタイルシートは次のようになります.
 
<xsl:template match="doc">
    <xsl:copy>
        <xsl:for-each-group  select="p" group-starting-with="p[@style='Heading1']">
            <xsl:apply-templates select="."/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>
<xsl:template match="p[@style='Heading1']">
    <xsl:element name="chapter">
        <title><xsl:value-of select="."/></title>
        <xsl:for-each-group select="current-group() except ." group-starting-with="p[@style='Heading2']">
            <xsl:apply-templates select="."/>
        </xsl:for-each-group>
    </xsl:element>
</xsl:template>
<xsl:template match="p[@style='Heading2']">
    <xsl:element name="section">
        <title><xsl:value-of select="."/></title>
        <xsl:apply-templates select="current-group() except ."/>
    </xsl:element>
</xsl:template>
<xsl:template match="p">
    <xsl:copy>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>
 
結果は、次のようになります.
 
<?xml version="1.0" encoding="UTF-8"?>
<doc>
   <chapter>
      <title>はじめに</title>
      <p>この文書は、平坦なXMLから構造的なXMLを作る実験データです.</p>
      <section>
         <title>見出しレベル2</title>
         <p>見出しレベル2の段落です.</p>
         <p>まだ続きます.</p>
         <p>これでおしまい.</p>
      </section>
      <section>
         <title>見出しレベル2の2番目です.</title>
         <p>はたして構造化できるでしょうか?</p>
      </section>
   </chapter>
   <chapter>
      <title>XSLTプロセッサ</title>
      <p>現在主に使われているプロセッサにはXSLT1.0とXSLT2.0のものがあります.</p>
      <section>
         <title>XSLT1.0のプロセッサ</title>
         <p>MSXMLやXALANがあります.</p>
      </section>
      <section>
         <title>XSLT2.0のプロセッサ</title>
         <p>SaxonやAltovaがあります.</p>
         <p>これで文章はおしまいです.</p>
      </section>
   </chapter>
</doc>
 
テンプレートのポイントは、
1. xsl:for-each-groupの直下では、コンテキストは、「そのグループを開始する」要素になっていることです.
2. また、そこから<xsl:apply-templates select="."/>で呼ばれたテンプレートでは、current-group()が引き続き有効であることです.
3. あと、初めて出てきたexceptでグループの先頭の要素を除いて処理を行うことです.exceptはその名のとおり、対象のXPath式からexceptの右に書かれた要素を対象からはずしてくれる便利なオペレータです.
 
いかがでしょう?それらしい文書が作れました.WordMLのw:bodyの子要素は基本的にw:p(段落)、w:tbl(テーブル)だけです.あとこれにテーブルの処理も加えれば本格的な処理ができるのではないでしょうか.