Ответ 1
Взгляните на функции position()
, count()
и last()
; например, test="position() < last()"
.
Учитывая данные XML:
<root> <item>apple</item> <item>orange</item> <item>banana</item> </root>
Я могу использовать эту разметку XSLT:
... <xsl:for-each select="root/item"> <xsl:value-of select="."/>, </xsl:for-each> ...
чтобы получить этот результат:
яблоко, апельсин, банан,
но как создать список, в котором отсутствует последняя запятая? Я предполагаю, что это можно сделать, делая что-то вроде:
... <xsl:for-each select="root/item"> <xsl:value-of select="."/> <xsl:if test="...">,</xsl:if> </xsl:for-each> ...
но каково должно быть тестовое выражение?
Мне нужен способ выяснить, сколько времени осталось в списке и где я сейчас в списке, или, в качестве альтернативы, если я в настоящее время обрабатываю последний элемент в списке (что означает, что мне все равно, как долго есть или что такое текущая позиция).
Взгляните на функции position()
, count()
и last()
; например, test="position() < last()"
.
Это довольно распространенный шаблон:
<xsl:for-each select="*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
Для параметра XSLT 2.0 вы можете использовать атрибут separator
на xsl:value-of
.
Это 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,', ')"/>
<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>
Роберт дал ответ classis not(position() = last())
. Это требует, чтобы вы обработали весь текущий список node, чтобы получить размер контекста, и в больших входных документах это может заставить конверсию потреблять больше памяти. Поэтому я обычно инвертирую тест, чтобы быть первым.
<xsl:for-each select="*">
<xsl:if test="not(position() = 1)>, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
Простой однострочный интерфейс 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
EDIT:
Вот комментарий Роберта Россни к этому ответу:
Этот довольно непрозрачный код для человека читать. Это требует, чтобы вы знали два неочевидные вещи о XSLT: 1) что функция подстроки, если ее индекс выходит за пределы диапазона и 2) что логические значения могут быть неявно преобразованные в числовые.
, и вот мой ответ:
Ребята, никогда не стесняйтесь изучать что-то новое. На самом деле это все переполнение стека, не так ли?:)
Это то, как я заработал у меня. Я проверил это против вашего списка:
<?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>