XSLTでフラットな文書を構造化 msxsl:script版

このページの目次
  1. 概要
  2. ソースコード
  3. 補足
  4. 参考文献
  5. 関連文書

概要

見出し要素(hn要素)に基づき、次のようにdiv要素を追加するXSLTスタイルシートです。

MicrosoftのXSLT処理系の独自拡張要素であるmsxsl:scriptを利用することで、構造化処理の複雑な部分をC#で記述し、XSLTテンプレートの記述を簡素化しました。

変換前:

<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>構造化サンプル</title>
  </head>
  <body>
    <h1>見出し1</h1>
    <h2>見出し2-1</h2>
    <p>本文a</p>
    <h3>見出し3-1</h3>
    <p>本文b</p>
    <h3>見出し3-2</h3>
    <p>本文c</p>
    <h2>見出し2-2</h2>
    <p>本文d</p>
  </body>
</html>

変換後:

<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>構造化サンプル</title>
  </head>
  <body>
    <div class="section">
      <h1>見出し1</h1>
      <div class="section">
        <h2>見出し2-1</h2>
        <p>本文a</p>
        <div class="section">
          <h3>見出し3-1</h3>
          <p>本文b</p>
        </div>
        <div class="section">
          <h3>見出し3-2</h3>
          <p>本文c</p>
        </div>
      </div>
      <div class="section">
        <h2>見出し2-2</h2>
        <p>本文d</p>
      </div>
    </div>
  </body>
</html>

ソースコード

<?xml version="1.0"?>
<xsl:stylesheet
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:script="http://refluxflow.net/2014/XSLT/Scripting"
  exclude-result-prefixes="xsl msxsl script"
  version="1.0">

  <msxsl:script language="CSharp" implements-prefix="script">
    <msxsl:using namespace="System.Collections.Generic"/>
<![CDATA[
public XPathNavigator[] GetSectionContentNodes(XPathNodeIterator nodeSet, int sectionLevel)
{
    var resultNodeSet = new List<XPathNavigator>();
    bool isHeadingFound = false;
    foreach (XPathNavigator node in nodeSet) {
        int headingLevel;
        if (TryGetHeadingLevel(node, out headingLevel)) {
            if (headingLevel <= sectionLevel) {
                // 同位または上位の見出し要素が見つかったら探索を終了する。
                break;
            } else if (headingLevel == sectionLevel + 1 || !isHeadingFound) {
                // 1つ下位の見出し要素は出力する。
                // 2つ以上下位でも最初の見出し要素は出力する(見出しレベルが飛んでいる場合)。
                resultNodeSet.Add(node);
                isHeadingFound = true;
                continue;
            } else {
                continue;
            }
        }
        if (!isHeadingFound) {
            // 見出し要素が見つかるまでは、見出し要素以外のノードも出力する。
            resultNodeSet.Add(node);
        }
    }
    return resultNodeSet.ToArray();
}

bool TryGetHeadingLevel(XPathNavigator node, out int headingLevel)
{
    if (node.NodeType != XPathNodeType.Element) {
        headingLevel = 0;
        return false;
    }
    var match = Regex.Match(node.LocalName, @"\Ah(\d+)\z");
    if (!match.Success) {
        headingLevel = 0;
        return false;
    }
    headingLevel = Int32.Parse(match.Groups[1].Value);
    return true;
}
]]>
  </msxsl:script>

  <xsl:output
    method="xml"
    version="1.0"
    encoding="UTF-8"
    indent="yes"/>

  <xsl:template match="*">
    <xsl:copy>
      <xsl:apply-templates select="attribute::node()|child::node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="attribute::node()">
    <xsl:copy/>
  </xsl:template>

  <!-- body要素にマッチするテンプレート -->
  <xsl:template match="*[local-name() = 'body']">
    <xsl:copy>
      <xsl:apply-templates select="attribute::node()"/>
      <xsl:apply-templates select="script:GetSectionContentNodes(child::node(), 0)"/>
    </xsl:copy>
  </xsl:template>

  <!-- 見出し要素にマッチするテンプレート -->
  <xsl:template match="*[starts-with(local-name(), 'h') and boolean(number(substring(local-name(), 2)))]">
    <xsl:variable name="heading-level" select="number(substring(local-name(), 2))"/>
    <div class="section">
      <xsl:copy>
        <xsl:apply-templates select="attribute::node()|child::node()"/>
      </xsl:copy>
      <xsl:apply-templates select="script:GetSectionContentNodes(following-sibling::node(), $heading-level)"/>
    </div>
  </xsl:template>

</xsl:stylesheet>

補足

自由に書き換えて再利用して構いませんが、ご使用は自己責任でお願いします。

参考文献

msxsl:script を使用したスクリプト ブロック
msxsl:scriptの説明です(MSDNライブラリ)。
XslCompiledTransform クラス (System.Xml.Xsl)
XslCompiledTransformクラスの説明です(MSDNライブラリ)。

関連文書

XSLTでフラットな文書を構造化
XSLT 1.0の機能だけでフラットな文書を構造化します。