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
を書こうとしたのか……。