我有这样的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的元素,只选择连续的元素。但是我如何计算连续的?或者我这样做是错误的吗?
答案 0 :(得分:8)
这很棘手,但可行(长时间预读,对不起)。
根据XPath轴(根据定义,不是连续的)“连续性”的关键是检查相反方向中“最先满足条件”的最近节点是否也是一个“开始”手头的系列:
a b <- first node to fulfill the condition, starts series 1 b <- series 1 b <- series 1 a b <- first node to fulfill the condition, starts series 2 b <- series 2 b <- series 2 a
在您的情况下,系列包含<span>
个x
节点,@class
中包含字符span[contains(concat(' ', @class, ' '),' x ')]
:
<span>
请注意,我将空格连接起来以避免误报。
开始一系列的x
(即“首先满足条件”的系列)可以被定义为在其类中具有<span>
而不是直接在另一个{{1}之前的系列x
。还有一个not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
:
<xsl:if>
我们必须在following-sibling::span
中检查这个条件,以避免模板为一系列节点生成输出(即模板只对“起始节点”执行实际工作)。
现在到了棘手的部分。
从这些“起始节点”中的每一个中,我们必须选择在其类中具有x
的所有span
个节点。还包括当前. | following-sibling::span[contains(concat(' ', @class, ' '),' x ')]
来说明只有一个元素的系列。好的,很简单:
span
对于其中的每一个,我们现在发现他们的最接近的“起始节点”是否与模板正在处理的那个相同(即,它开始他们的系列) 。这意味着:
他们必须是系列的一部分(即他们必须跟x
preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]
span
现在删除其起始节点与当前系列起始器不同的任何span
。这意味着我们会检查前任兄弟x
(其中有一个span
),其中x
前面没有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>
是的,我知道它不漂亮。基于1
<x>234</x>
5
<x>6</x>
7
<x>8</x>
的解决方案效率更高,Dimitre的答案显示了它。
使用您的样本输入,将生成此输出:
{{1}}
答案 1 :(得分:5)
<强>予。 XSLT解决方案:
我想要的是使用XSLT样式表来放置所有内容 其class属性包含
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>
<强> II。 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>
当在同一个XML文档(上面)上应用此XSLT 2.0转换时,会生成所需的“理想”结果:
1<x>2<y>3</y>4</x>5<x>6</x>7<x>8</x>
答案 2 :(得分:2)
感谢您的解决方案。与此同时,我设法用一种完全不同的策略组合起来。我只是在学习这个项目的XSLT,我读过的最有用的东西是XSLT就像函数编程一样。所以我在this指向正确的方向后使用递归写了一些东西:
<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()
或密钥更有效还是更低效,但我当然从你的解决方案中学到了一些东西!