XMLの親の属性を子に移動する(XSLTで)
ふつうはどうやるのが一般的なんだろう。やはりXSLTかな。
ということでね、一般的な手段であるXSLTでやってみましょう。
元記事の課題は「table要素のid属性を、子のcaptionに移す」ですね。こんな簡単な課題、さぞ簡潔な処理になるに違いない。
課題を達成するスタイルシート
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="fn">
<xsl:output method="xhtml" html-version="5.0" />
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template
match="table[fn:exists(@id)][
fn:exists(
child::caption[fn:not(fn:exists(@id))]
)
]">
<xsl:copy>
<!--2022-07-28 修正 xsl:copy-of
select="attribute::*[fn:not(fn:local-name() = 'id')]"/-->
<xsl:copy-of
select="attribute::* except @id"/>
<xsl:apply-templates >
<xsl:with-param name="tableId"
select="./@id"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template
match="table[fn:exists(@id)]/caption[fn:not(fn:exists(@id))]" >
<xsl:param name="tableId" as="attribute()"/>
<xsl:copy>
<xsl:copy-of select="attribute::*"/>
<xsl:attribute name="id" select="$tableId"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:transform>
30行以上ある……どうして……。
スタイルシートの説明
<xsl:output>について。XSLT 3.0ではhtml5出力ができるというのは何度か別記事でも言及していますが、HTML5のXHTML記法も通っている、ようにみえますね。Saxon-HEで試しています。
<xsl:mode>。今回XSLT 3.0な理由の一番はこれ。@on-no-matchで、スタイルシート中でマッチしないノードに対する挙動をshallow-copy、つまりコピーして次のノード処理に移っています。2.0までは@match="*"のンプレートを書いたりがあったので結構大事な進化です。
テンプレート部分。一見複雑そうにも見えますが、起こりそうなパターンの大体に対応した上でのパターン指定です。<xsl:copy>は、例えば複数パターンの要素にマッチさせて、それぞれ要素名を保った処理をするときに重宝します。つまり今回はただのお洒落。<xsl:copy-of>はdeep-copyな処理ですのでこれはこれでattribute以外処理させるのは怖いやつです。
<table>に対しては、「id属性を持ち」「子にid属性の無い<caption>がいる」パターンでid属性以外の残りの属性コピーを行い、子の処理に対しパラメータとして自身の持っていたidの値を渡しています。
<caption>も、「id属性を持った<table>の子」で「自身はid属性を持たない」パターンです。先程の<table>パターンも「子の<caption>にid属性が無い」パターンに限定したものだったので、id属性のロストは多分起こらない、はず。パラメータとしてtableIdを受けとるので、これを自身のidとして挿入します。<xsl:apply-templates/>にパラメータ渡せるのはXSLT 2.0からだったか。
Discussion
これだと、たとえば次のような
tableが来たときに@foo:idが消失するので、素直にexceptしたほうが、より安全でもあるかなと思います:確かに複数の名前空間でidを別で付けかねないXMLには憶えがあります。記事を修正します。
ありがとうございます。
あと、
この
@default-modeはbooleanではないはずなので、何かの書き間違いではないかと思いました。(実害はありませんが)ありがとうございます。修正しました。
xsl:modeは書きなれていとはいえ、そもそもどうしてここで
@default-modeを書こうとしたのか……。