>我有这样的XML:
<span>1</span>
<span class="x">2</span>
<span class="x y">3</span>
<span class="x">4</span>
<span>5</span>
<span class="x">6</span>
<span>7</span>
<span class="x">8</span>
我想要的是使用 XSLT 样式表将 class
属性包含x
的所有元素的内容放入一个<x>
元素中。所以输出应该是这样的:
1 <x>234</x> 5 <x>6</x> 7 <x>8</x>
(或者,理想情况下,
1 <x>2<y>3</y>4</x> 5 <x>6</x> 7 <x>8</x>
但当我解决了这个问题时,这是一个需要解决的问题。
这是我的 XSLT 的相关片段:
<xsl:template match="span[contains(@class,'x') and preceding-sibling::span[1][not(contains(@class,'x'))]]">
<x><xsl:for-each select=". | following-sibling::span[contains(@class,'x')]">
<xsl:value-of select="text()"/>
</xsl:for-each></x>
</xsl:template>
<xsl:template match="span[contains(@class,'x') and preceding-sibling::span[1][contains(@class,'x')]]">
</xsl:template>
<xsl:template match="span">
<xsl:value-of select="text()"/>
</xsl:template>
这会产生什么:
1 <x>23468</x> 5 <x>68</x> 7 <x>8</x>
我很确定我必须在 XPath 表达式中使用计数,这样它就不会选择具有类 x 的以下所有元素,而只会选择连续的元素。但是我怎么能数出连续的呢?还是我这样做的方式不对?
这很棘手,但可行(提前阅读很长时间,对此感到抱歉(。
就 XPath 轴而言,"连续性"的关键(根据定义不是连续的(是检查"首先满足条件"的相反方向上最近的节点是否也是"启动"手头序列的节点:
一个B <- 第一个满足条件的节点,启动系列 1B <- 系列 1B <- 系列 1一个B <- 第一个满足条件的节点,启动系列 2B <- 系列 2B <- 系列 2一个
在您的情况下,序列由<span>
节点组成,这些节点的@class
中具有字符串x
:
span[contains(concat(' ', @class, ' '),' x ')]
请注意,我连接空格以避免误报。
开始一个系列的<span>
(即"首先满足条件"的序列(可以定义为在其类中具有x
并且不直接在另一个具有x
<span>
之前:
not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
我们必须在<xsl:if>
中检查此条件,以避免模板为串联的节点生成输出(即,模板将仅对"启动节点"执行实际工作(。
现在进入棘手的部分。
从这些"启动节点"中的每一个中,我们必须选择在其类中具有x
的所有following-sibling::span
节点。还包括当前span
,以考虑只有一个元素的系列。好的,很简单:
. | following-sibling::span[contains(concat(' ', @class, ' '),' x ')]
对于其中的每一个,我们现在找出它们最接近的"启动节点"是否与模板正在处理的节点相同(即开始其系列的节点(。这意味着:
- 它们
必须是系列的一部分(即它们必须遵循带有
x
的span
(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]
现在删除其启动器节点与当前系列启动器不相同的任何
span
。这意味着我们检查任何前面的同级span
(有一个x
(,它本身没有直接带有x
的span
:preceding-sibling::span[contains(concat(' ', @class, ' '),' x ')][ not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]) ][1]
然后我们使用
generate-id()
来检查节点身份。如果找到的节点与$starter
相同,则当前跨度属于连续序列
。
把所有的东西放在一起:
<xsl:template match="span[contains(concat(' ', @class, ' '),' x ')]">
<xsl:if test="not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])">
<xsl:variable name="starter" select="." />
<x>
<xsl:for-each select="
. | following-sibling::span[contains(concat(' ', @class, ' '),' x ')][
preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]
and
generate-id($starter)
=
generate-id(
preceding-sibling::span[contains(concat(' ', @class, ' '),' x ')][
not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
][1]
)
]
">
<xsl:value-of select="text()" />
</xsl:for-each>
</x>
</xsl:if>
</xsl:template>
是的,我知道它并不漂亮。有一种基于<xsl:key>
的解决方案更有效,Dimitre的回答表明了这一点。
使用示例输入,将生成以下输出:
1
<x>234</x>
5
<x>6</x>
7
<x>8</x>
I. XSLT 解决方案:
我想要的是使用 XSLT 样式表来放置所有内容 其类属性包含
x
到一个<x>
元素中的元素。所以 输出应如下所示:1 <x>234</x> 5 <x>6</x> 7 <x>8</x>
此转换:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match=
"span[contains(concat(' ', @class, ' '),
' x ')
]"
use="generate-id(preceding-sibling::span
[not(contains(concat(' ', @class, ' '),
' x '))
][1]
)
"/>
<xsl:template match=
"span[contains(concat(' ', @class, ' '), ' x ')
and
not(contains(concat(' ', preceding-sibling::span[1]/@class, ' '),
' x '
)
)
]"
>
<x>
<xsl:apply-templates mode="inGroup" select=
"key('kFollowing',
generate-id(preceding-sibling::span [not(contains(concat(' ', @class, ' '), ' x ')
)
][1]
)
)
"/>
</x>
</xsl:template>
<xsl:template match=
"span[contains(concat(' ', @class, ' '), ' x ')
and
contains(concat(' ', preceding-sibling::span[1]/@class, ' '),
' x '
)
]
"/>
</xsl:stylesheet>
当应用于提供的 XML 文档(包装成单个顶部元素html
使其格式正确(时:
<html>
<span>1</span>
<span class="x">2</span>
<span class="x y">3</span>
<span class="x">4</span>
<span>5</span>
<span class="x">6</span>
<span>7</span>
<span class="x">8</span>
</html>
产生所需的正确结果:
1<x>234</x>5<x>6</x>7<x>8</x>
然后是"理想"的补充:
或者,理想情况下,
1 <x>2<y>3</y>4</x> 5 <x>6</x> 7 <x>8</x>
但当我解决了这个问题时,这是一个需要解决的问题。
只需将此模板添加到上述解决方案中:
<xsl:template mode="inGroup" match=
"span[contains(concat(' ', @class, ' '),
' y '
)
]">
<y><xsl:value-of select="."/></y>
</xsl:template>
将如此修改的解决方案应用于同一 XML 文档时,将再次生成(新的(所需结果:
1<x>2<y>3</y>4</x>5<x>6</x>7<x>8</x>
二、XSLT 2.0 解决方案:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="span" group-adjacent=
"contains(concat(' ',@class,' '), ' x ')">
<xsl:sequence select=
"if(current-grouping-key())
then
my:formatGroup(current-group())
else
data(current-group())
"/>
</xsl:for-each-group>
</xsl:template>
<xsl:function name="my:formatGroup" as="node()*">
<xsl:param name="pGroup" as="node()*"/>
<x>
<xsl:apply-templates select="$pGroup"/>
</x>
</xsl:function>
<xsl:template match=
"span[contains(concat(' ',@class, ' '), ' y ')]">
<y><xsl:apply-templates/></y>
</xsl:template>
</xsl:stylesheet>
当此 XSLT 2.0 转换应用于同一 XML 文档(如上(时,将生成所需的"理想"结果:
1<x>2<y>3</y>4</x>5<x>6</x>7<x>8</x>
感谢您的解决方案。与此同时,我设法用一种完全不同的策略把一些东西放在一起。我只是在为这个项目学习XSLT,我读过的最有用的事情是XSLT就像函数式编程。所以我用递归写了一些东西,在被指出正确的方向之后:
<xsl:template match="span[
contains(@class,'x')
and
preceding-sibling::span[1][
not(contains(@class,'x'))
]
]">
<x><xsl:value-of select="text()"/>
<xsl:call-template name="continue">
<xsl:with-param name="next" select="following-sibling::span[1]"/>
</xsl:call-template>
</x>
</xsl:template>
<xsl:template name="continue">
<xsl:param name="next"/>
<xsl:choose>
<xsl:when test="$next[contains(@class,'x')]">
<xsl:apply-templates mode="x" select="$next"/>
<xsl:call-template name="continue">
<xsl:with-param name="next" select="$next/following-sibling::span[1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/><!-- Do nothing -->
</xsl:choose>
</xsl:template>
<xsl:template match="span[
contains(@class,'x')
and
preceding-sibling::span[1][
contains(@class,'x')
]
]"/>
<xsl:template match="span">
<xsl:value-of select="text()"/>
</xsl:template>
<xsl:template mode="x" match="span[contains(@class,'y')]">
<y><xsl:value-of select="text()"/></y>
</xsl:template>
<xsl:template mode="x" match="span">
<xsl:value-of select="text()"/>
</xsl:template>
我不知道这是否比使用generate-id()
或密钥更有效率,但我确实从您的解决方案中学到了一些东西!