我正在尝试匹配一系列以逗号分隔的xml标记,然后对整个节点组加上文本应用xslt转换。例如,给定以下部分XML:
<p>Some text here
<xref id="1">1</xref>,
<xref id="2">2</xref>,
<xref id="3">3</xref>.
</p>
我想最终:
<p>Some text here <sup>1,2,3</sup>.</p>
此时,一个更加混乱的替代方案也是可以接受的:
<p>Some text here <sup>1</sup><sup>,</sup><sup>2</sup><sup>,</sup><sup>3</sup>.</p>
我有从单个外部参照到sup的转换:
<xsl:template match="xref"">
<sup><xsl:apply-templates/></sup>
</xsl:template>
但是我对如何匹配用逗号分隔的一组节点感到茫然。
感谢。
答案 0 :(得分:3)
更新:感谢@ Flynn1179提醒我解决方案没有产生完全想要的输出,我稍微修改了它。 现在生成了想要的“好”格式。
此XSLT 1.0转换:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|@*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match=
"xref[not(preceding-sibling::node()[1]
[self::text() and starts-with(.,',')]
)
]">
<xsl:variable name="vBreakText" select=
"following-sibling::text()[not(starts-with(.,','))][1]"/>
<xsl:variable name="vPrecedingTheBreak" select=
"$vBreakText/preceding-sibling::node()"/>
<xsl:variable name="vFollowing" select=
".|following-sibling::node()"/>
<xsl:variable name="vGroup" select=
"$vFollowing[count(.|$vPrecedingTheBreak)
=
count($vPrecedingTheBreak)
]
"/>
<sup>
<xsl:apply-templates select="$vGroup" mode="group"/>
</sup>
<xsl:apply-templates select="$vBreakText"/>
</xsl:template>
<xsl:template match="text()" mode="group">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
</xsl:stylesheet>
应用于以下XML文档(基于提供的文档,但更加复杂和有趣):
<p>Some text here
<xref id="1">1</xref>,
<xref id="2">2</xref>,
<xref id="3">3</xref>.
<ttt/>
<xref id="4">4</xref>,
<xref id="5">5</xref>,
<xref id="6">6</xref>.
<zzz/>
</p>
产生完全正确的结果:
<p>Some text here
<sup>1,2,3</sup>.
<ttt/>
<sup>4,5,6</sup>.
<zzz/>
</p>
<强>解释强>:
我们使用“细粒度”身份规则,按规则处理文档逐节点并按“原样”复制匹配的节点
我们使用匹配任何xref
元素的模板覆盖标识规则,该元素是xref
元素组中的第一个元素,每个元素(但最后一个)后面跟着以','字符开头的直接text-node-sibling。在这里,我们找到第一个破坏规则的text-node-sibling(它的起始字符不是','。
然后我们使用Kayessian(在@Michael Kay之后)公式找到组中的所有节点,用于两个节点集的交集。此公式为:$ns1[count(.|$ns2) = count($ns2)]
然后我们以名为“group”的模式处理组中的所有节点。
最后,我们将模板(以匿名模式)应用于中断文本节点(即该组后面的第一个节点),以便继续处理链。
答案 1 :(得分:2)
第二种替代方案可以通过
实现<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p/text()[normalize-space() = ',' and preceding-sibling::node()[1][self::xref]]">
<sup>,</sup>
</xsl:template>
<xsl:template match="xref">
<sup>
<xsl:apply-templates/>
</sup>
</xsl:template>
</xsl:stylesheet>
答案 2 :(得分:2)
有趣的问题。 1。
这是一个XSLT 2.0解决方案:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="comma-regex">^\s*,\s*$</xsl:variable>
<!-- Identity transform -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Don't directly process xrefs that are second or later in a comma-separated series.
Note that this template has a higher default priority than the following one,
because of the predicate. -->
<xsl:template match="xref[preceding-sibling::node()[1]/
self::text()[matches(., $comma-regex)]/
preceding-sibling::*[1]/self::xref]" />
<!-- Don't directly process comma text nodes that are in the middle of a series. -->
<xsl:template match="text()[matches(., $comma-regex) and
preceding-sibling::*[1]/self::xref and following-sibling::*[1]/self::xref]" />
<!-- for xrefs that first (or solitary) in a comma-separated series: -->
<xsl:template match="xref">
<sup>
<xsl:call-template name="process-xref-series">
<xsl:with-param name="next" select="." />
</xsl:call-template>
</sup>
</xsl:template>
<xsl:template name="process-xref-series">
<xsl:param name="next"/>
<xsl:if test="$next">
<xsl:value-of select="$next"/>
<xsl:variable name="followingXref"
select="$next/following-sibling::node()[1]/
self::text()[matches(., $comma-regex)]/
following-sibling::*[1]/self::xref"/>
<xsl:if test="$followingXref">
<xsl:text>,</xsl:text>
<xsl:call-template name="process-xref-series">
<xsl:with-param name="next" select="$followingXref"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
(如果我们可以对输入做出一些假设,这可以简化。)
针对您提供的示例输入运行,结果为:
<p>Some text here
<sup>1,2,3</sup>.
</p>
答案 3 :(得分:2)
对于你的“凌乱的替代方案”,有一个几乎无足轻重的解决方案:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="xref">
<sup>
<xsl:apply-templates />
</sup>
</xsl:template>
<xsl:template match="text()[normalize-space(.)=',']">
<sup>,</sup>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*| node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
编辑:我刚刚注意到它几乎是Martin解决方案的一个克隆,除非在逗号上没有额外检查前面的外部参照元素。他可能更安全:))
对您的首选结果稍微不那么简单的解决方案,尽管只有在任何xref
标记中只有一个p
标记集合时才有效。你没有提到多个集合的可能性,即使有,我也认为它们不太可能属于同一个包含p
标签。如果可能发生这种情况,可以进一步扩展它以实现这一点,尽管它会变得更加复杂。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="xref[not(preceding-sibling::text()[normalize-space(.)=','])]">
<sup>
<xsl:value-of select="." />
<xsl:for-each select="following-sibling::text() | following-sibling::xref">
<xsl:if test="following-sibling::text()[substring(.,1,1)='.']">
<xsl:value-of select="normalize-space(.)" />
</xsl:if>
</xsl:for-each>
</sup>
</xsl:template>
<xsl:template match="xref | text()[normalize-space(.)=',']" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*| node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
答案 4 :(得分:1)
如果您可以使用XSLT 2.0(例如使用Saxon 9或AltovaXML或XQSharp),那么这是一个XSLT 2.0解决方案,它应该产生您要求的第一个输出:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:for-each-group select="node()" group-adjacent="self::xref or self::text()[normalize-space() = ',']">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<sup>
<xsl:value-of select="current-group()/normalize-space()" separator=""/>
</sup>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>