可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Given this XML data:
<root>
<item>apple</item>
<item>orange</item>
<item>banana</item>
</root>
I can use this XSLT markup:
...
<xsl:for-each select="root/item">
<xsl:value-of select="."/>,
</xsl:for-each>
...
to get this result:
apple, orange, banana,
but how do I produce a list where the last comma is not present? I assume it can be done doing something along the lines of:
...
<xsl:for-each select="root/item">
<xsl:value-of select="."/>
<xsl:if test="...">,</xsl:if>
</xsl:for-each>
...
but what should the test expression be?
I need some way to figure out how long the list is and where I currently am in the list, or, alternatively, if I am currently processing the last element in the list (which means I don't care how long it is or what the current position is).
回答1:
Take a look at the position()
, count()
and last()
functions; e.g., test="position() < last()"
.
回答2:
This is a pretty common pattern:
<xsl:for-each select="*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
回答3:
For an XSLT 2.0 option, you can use the separator
attribute on xsl:value-of
.
This xsl:value-of
:
<xsl:value-of select="/root/item" separator=", "/>
would produce this output:
apple, orange, banana
You could also use more than just a comma for a separator. For example, this:
<xsl:text>'</xsl:text>
<xsl:value-of select="/root/item" separator="', '"/>
<xsl:text>'</xsl:text>
Would produce the following output:
'apple', 'orange', 'banana'
Another XSLT 2.0 option is string-join()
...
<xsl:value-of select="string-join(/*/item,', ')"/>
回答4:
<xsl:if test="following-sibling::*">,</xsl:if>
or (perhaps more efficient, but you'd have to test):
<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>
回答5:
Robert gave the classis not(position() = last())
answer. This requires you to process the whole current node list to get context size, and in large input documents this might make the conversion consume more memory. Therefore, I normally invert the test to be the first thing
<xsl:for-each select="*">
<xsl:if test="not(position() = 1)>, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
回答6:
A simple XPath 1.0 one-liner:
concat(., substring(',', 2 - (position() != last())))
Put it into this transformation:
<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>
and apply it to the XML document:
<root>
<item>apple</item>
<item>orange</item>
<item>banana</item>
</root>
to get the wanted result:
apple,orange,banana
EDIT:
Here is a comment from Robert Rossney to this answer:
That's pretty opaque code for a human
to read. It requires you to know two
non-obvious things about XSLT: 1) what
the substring function does if its
index is out of range and 2) that
logical values can be implicitly
converted to numerical ones.
and here is my answer:
Guys, never shy from learning something new. In fact this is all Stack Overflow is about, isn't it? :)
回答7:
This is the way I got it working for me.
I tested this against your list:
<?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>