XSLTでフラットな文書を構造化 msxsl:script版
概要
見出し要素(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>
補足
- Microsoft独自機能を使用しているため、実行できる処理系は.NET FrameworkのXslCompiledTransformクラスだけです。
- 変換前のXMLでは、見出し要素はすべて同じ要素の子要素である必要があります。
- 要素名に名前空間接頭辞がついていても大丈夫です(例えばxht:h1でも可)。
- h7やh8があっても処理できます。
自由に書き換えて再利用して構いませんが、ご使用は自己責任でお願いします。
参考文献
- msxsl:script を使用したスクリプト ブロック
- msxsl:scriptの説明です(MSDNライブラリ)。
- XslCompiledTransform クラス (System.Xml.Xsl)
- XslCompiledTransformクラスの説明です(MSDNライブラリ)。
関連文書
- XSLTでフラットな文書を構造化
- XSLT 1.0の機能だけでフラットな文書を構造化します。