使用变量的 XSLT 模板匹配
XSLT template matching using variable
在我的源代码 XML 中,我有一个包含 Xpath 表达式列表的元素,指向同一 XML 中的不同节点。这是一个示例,其中 Xpath 位于 /root/Properties[@name='changed']
-
<root xmlns="http://www.example.org">
<main>
<child1>123</child1>
<child2>456</child2>
<subChildren>
<subChild1>321</subChild1>
<subChild2>644</subChild2>
</subChildren>
</main>
<Properties name="changed">/t:root/t:main/t:child2|/t:root/t:main/t:subChildren/t:subChild1</Properties>
</root>
我正在编写 XSLT 以将属性添加到 XPath 中指定的所有节点。下面的 XLST 完成了这项工作,但是请注意,xpath 是硬编码在第二个模板的匹配值中的 -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:t="http://www.example.org" version='2.0'>
<xsl:variable name="xpaths" select="/t:root/t:Properties[@name='changed']" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match='/t:root/t:main/t:child2|/t:root/t:main/t:subChildren/t:subChild1'>
<xsl:call-template name="addChanged" />
</xsl:template>
<xsl:template name="addChanged">
<xsl:copy>
<xsl:attribute name="changed">true</xsl:attribute>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
我想要相同的样式表来处理模板匹配属性中的变量,就像这样 -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:t="http://www.example.org" version='2.0'>
<xsl:variable name="xpaths" select="/t:root/t:Properties[@name='changed']" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match='$xpaths'>
<xsl:call-template name="addChanged" />
</xsl:template>
<xsl:template name="addChanged">
<xsl:copy>
<xsl:attribute name="changed">true</xsl:attribute>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
我如何完成这项工作?上面的样式表没有正确匹配模板中的节点。我正在使用 Saxon HE 9.6。
好吧,我希望您的方法不起作用的原因显而易见:在 XSLT 3.0 中,您可以在匹配模式中使用变量,但变量的值必须是一组节点,而不是用于选择这些节点的 XPath 表达式节点。
要评估作为字符串提供的 XPath 表达式,您需要 xsl:evaluate
<!-- Beaware of quoting -->
<xsl:variable name="path-to-changed-nodes"
select="'/t:root/t:Properties[@name=''changed'']'" as="xs:string"/>
<xsl:variable name="changed-nodes" as="node()*">
<xsl:evaluate xpath="$path-to-changed-nodes"
context-item="/"/>
</xsl:variable>
<xsl:template match="$changed-nodes">
...
</xsl:template>
在 Saxon 中,xsl:evaluate
指令需要 Saxon-PE 或更高版本。如果您无法说服某人将 Saxon-PE 许可证作为圣诞礼物送给您,另一种方法是将您的解决方案实现为两个转换的序列:首先生成包含所需匹配模式的样式表,然后执行该样式表。
稍后
事实上,对于 XSLT 3.0,有一种更简单的替代方法可以将样式表生成为 XML 源;你可以使用静态参数和影子变量(虽然我没有测试过)。
从匹配模式的影子变量开始:
<xsl:template _match="{$pattern}">...
然后定义静态变量$pattern:
<xsl:variable name="pattern" static="yes"
select="string($doc//t:root/t:Properties[@name='changed'])"/>
然后声明参数$doc
:
<xsl:param name="doc" static="yes" as="document-node()"/>
并在编译样式表时为静态参数 doc 提供一个值。
我不知道这是否适用于 Saxon-HE 9.6 - 可能不会,因为它在 XSLT 3.0 最终确定之前就已经出现了。
这是另一种相当原始的方法,但一次完成:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.example.org">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="paths" select="tokenize(root/Properties[@name='changed'], '\|')" />
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/t:', name())"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:if test="$path-to-me = $paths">
<xsl:attribute name="changed">true</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
在我的源代码 XML 中,我有一个包含 Xpath 表达式列表的元素,指向同一 XML 中的不同节点。这是一个示例,其中 Xpath 位于 /root/Properties[@name='changed']
-
<root xmlns="http://www.example.org">
<main>
<child1>123</child1>
<child2>456</child2>
<subChildren>
<subChild1>321</subChild1>
<subChild2>644</subChild2>
</subChildren>
</main>
<Properties name="changed">/t:root/t:main/t:child2|/t:root/t:main/t:subChildren/t:subChild1</Properties>
</root>
我正在编写 XSLT 以将属性添加到 XPath 中指定的所有节点。下面的 XLST 完成了这项工作,但是请注意,xpath 是硬编码在第二个模板的匹配值中的 -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:t="http://www.example.org" version='2.0'>
<xsl:variable name="xpaths" select="/t:root/t:Properties[@name='changed']" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match='/t:root/t:main/t:child2|/t:root/t:main/t:subChildren/t:subChild1'>
<xsl:call-template name="addChanged" />
</xsl:template>
<xsl:template name="addChanged">
<xsl:copy>
<xsl:attribute name="changed">true</xsl:attribute>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
我想要相同的样式表来处理模板匹配属性中的变量,就像这样 -
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:t="http://www.example.org" version='2.0'>
<xsl:variable name="xpaths" select="/t:root/t:Properties[@name='changed']" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match='$xpaths'>
<xsl:call-template name="addChanged" />
</xsl:template>
<xsl:template name="addChanged">
<xsl:copy>
<xsl:attribute name="changed">true</xsl:attribute>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
我如何完成这项工作?上面的样式表没有正确匹配模板中的节点。我正在使用 Saxon HE 9.6。
好吧,我希望您的方法不起作用的原因显而易见:在 XSLT 3.0 中,您可以在匹配模式中使用变量,但变量的值必须是一组节点,而不是用于选择这些节点的 XPath 表达式节点。
要评估作为字符串提供的 XPath 表达式,您需要 xsl:evaluate
<!-- Beaware of quoting -->
<xsl:variable name="path-to-changed-nodes"
select="'/t:root/t:Properties[@name=''changed'']'" as="xs:string"/>
<xsl:variable name="changed-nodes" as="node()*">
<xsl:evaluate xpath="$path-to-changed-nodes"
context-item="/"/>
</xsl:variable>
<xsl:template match="$changed-nodes">
...
</xsl:template>
在 Saxon 中,xsl:evaluate
指令需要 Saxon-PE 或更高版本。如果您无法说服某人将 Saxon-PE 许可证作为圣诞礼物送给您,另一种方法是将您的解决方案实现为两个转换的序列:首先生成包含所需匹配模式的样式表,然后执行该样式表。
稍后
事实上,对于 XSLT 3.0,有一种更简单的替代方法可以将样式表生成为 XML 源;你可以使用静态参数和影子变量(虽然我没有测试过)。
从匹配模式的影子变量开始:
<xsl:template _match="{$pattern}">...
然后定义静态变量$pattern:
<xsl:variable name="pattern" static="yes"
select="string($doc//t:root/t:Properties[@name='changed'])"/>
然后声明参数$doc
:
<xsl:param name="doc" static="yes" as="document-node()"/>
并在编译样式表时为静态参数 doc 提供一个值。
我不知道这是否适用于 Saxon-HE 9.6 - 可能不会,因为它在 XSLT 3.0 最终确定之前就已经出现了。
这是另一种相当原始的方法,但一次完成:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.example.org">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="paths" select="tokenize(root/Properties[@name='changed'], '\|')" />
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/t:', name())"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:if test="$path-to-me = $paths">
<xsl:attribute name="changed">true</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>