如何使用 XSLT/XPath 生成逗号分隔的列表?
Posted
技术标签:
【中文标题】如何使用 XSLT/XPath 生成逗号分隔的列表?【英文标题】:How do I generate a comma-separated list with XSLT/XPath? 【发布时间】:2010-10-14 13:46:26 【问题描述】:鉴于此 XML 数据:
我可以使用这个 XSLT 标记:
...要得到这个结果:
苹果、橙子、香蕉、
但是如何生成最后一个逗号不存在的列表?我认为它可以按照以下方式完成:
...但是测试表达式应该是什么?
我需要一些方法来确定列表有多长以及我当前在列表中的位置,或者,如果我当前正在处理列表中的最后一个元素(这意味着我不在乎它有多长是或当前位置是什么)。
【问题讨论】:
【参考方案1】:<xsl:if test="following-sibling::*">,</xsl:if>
或(也许更有效,但你必须测试):
<xsl:for-each select="*[1]">
<xsl:value-of select="."/>
<xsl:for-each select="following-sibling::*">
<xsl:value-of select="concat(',',.)"/>
</xsl:for-each>
</xsl:for-each>
【讨论】:
您能否详细说明该测试实际上在做什么? something:: 表示一个轴——在这种情况下,是下一个兄弟轴。有各种轴 - 跟随兄弟轴是那些具有相同父节点的节点,它们按照文档顺序跟随当前节点。这将检查是否存在任何此类节点。如果没有,我们就是最后一个。 这行得通,但“position()=last()”不必构建节点集然后对其进行测试。 XSLT 处理器可能不够聪明,无法知道它不需要编译每个后续节点的列表,如果需要编译,这将使其成为(大约)O(n^2) 操作。 (当然我的意思是“position() != last()”。魔鬼在细节中。) 我会发布一个替代方案,然后;-p【参考方案2】:看看position()
、count()
和last()
函数;例如,test="position() &lt; last()"
。
【讨论】:
这正是我想要的,谢谢!无论如何,这样的东西记录在哪里? XPath 规范? 看看 w3schools.com/xpath,他们对基本 xpath 语法和功能有一些很好的概述。 我有这行:<xsl:value-of select="@Courses"/>
,它显示了所有的 CSV,但它看起来不太友好。有没有办法代替逗号,为每个值用换行符分隔它们?
@SiKni8 请为此创建一个新问题。【参考方案3】:
一个简单的 XPath 1.0 单行代码:
concat(., substring(',', 2 - (position() != last())))
把它放到这个变换中:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:for-each select="*">
<xsl:value-of select=
"concat(., substring(',', 2 - (position() != last())))"
/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
并将其应用于 XML 文档:
<root>
<item>apple</item>
<item>orange</item>
<item>banana</item>
</root>
获得想要的结果:
apple,orange,banana
编辑:
这是 Robert Rossney 对此答案的评论:
这对于人类来说是相当不透明的代码 阅读。它需要你知道两个 关于 XSLT 的非显而易见的事情:1)什么 如果它的子字符串函数 索引超出范围和 2) 逻辑值可以隐含 转换为数字。
这是我的答案:
伙计们,从不羞于学习新事物。事实上,这就是 Stack Overflow 的全部内容,不是吗? :)
【讨论】:
这是非常不透明的代码供人类阅读。它要求您了解关于 XSLT 的两个不明显的事情:1)如果子字符串函数的索引超出范围,子字符串函数会做什么,以及 2)逻辑值可以隐式转换为数字值。 @Robert-Rossney 当然,它的部分价值就在于此!【参考方案4】:这是一个很常见的模式:
<xsl:for-each select="*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
【讨论】:
如果您迭代的任何值是空的,这将失败。这将导致尾随或双逗号。 @aaronbauman 我不会称之为失败,而是缺少if not empty
:)
对于我使用的“if”:罗伯特给出了not(position() = last())
的答案。这需要您处理整个当前节点列表以获取上下文大小,并且在大型输入文档中这可能会使转换消耗更多内存。因此,我通常将测试反转为第一件事
<xsl:for-each select="*">
<xsl:if test="not(position() = 1)>, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
【讨论】:
我在想你为什么不想这样做,却没有找到一个理由。 position()!=last() 如果您想排除序列中的某些项目,则将不起作用。例如,如果有这样的列表会发生什么: deliciousposition()=last()
测试以仅使用一步前瞻,但在除第一个项目之外的每个项目之前放置分隔符的解决方案绝对是首选,因为 (a)它根本不涉及前瞻,并且 (b) 它避免了对智能优化的任何依赖。【参考方案6】:
对于 XSLT 2.0 选项,您可以在 xsl:value-of
上使用 separator
属性。
这个xsl:value-of
:
<xsl:value-of select="/root/item" separator=", "/>
会产生这个输出:
apple, orange, banana
您还可以不只是使用逗号作为分隔符。例如,这个:
<xsl:text>'</xsl:text>
<xsl:value-of select="/root/item" separator="', '"/>
<xsl:text>'</xsl:text>
将产生以下输出:
'apple', 'orange', 'banana'
另一个 XSLT 2.0 选项是 string-join()
...
<xsl:value-of select="string-join(/*/item,', ')"/>
【讨论】:
【参考方案7】:这就是我让它为我工作的方式。 我根据您的列表对此进行了测试:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="root">
<xsl:call-template name="comma-join"><xsl:with-param name="list" select="item"/></xsl:call-template>
</xsl:template>
<xsl:template name="comma-join">
<xsl:param name="list" />
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
【讨论】:
以上是关于如何使用 XSLT/XPath 生成逗号分隔的列表?的主要内容,如果未能解决你的问题,请参考以下文章