Строка XSLT concat, удалите последнюю запятую
Мне нужно создать строку с использованием XSLT и разделить каждую строку запятой, но не включать запятую после последней строки. В моем примере ниже у меня будет конечная запятая, если у меня есть Distribution node, а не Note node, например. Я вообще не знаю, как построить строку как переменную, а затем усечь последний символ в XSLT. Также используется механизм Microsoft XSLT.
Моя строка =
<xsl:if test="Locality != ''">
<xsl:value-of select="Locality"/>,
</xsl:if>
<xsl:if test="CollectorAndNumber != ''">
<xsl:value-of select="CollectorAndNumber"/>,
</xsl:if>
<xsl:if test="Institution != ''">
<xsl:value-of select="Institution"/>,
</xsl:if>
<xsl:if test="Distribution != ''">
<xsl:value-of select="Distribution"/>,
</xsl:if>
<xsl:if test="Note != ''">
<xsl:value-of select="Note"/>
</xsl:if>
[Человек там должен быть лучшим способом войти в это текстовое поле вопроса:(]
Ответы
Ответ 1
Это очень легко выполнить с помощью XSLT (нет необходимости фиксировать результаты в переменной или использовать специальные именованные шаблоны):
I. XSLT 1.0:
<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=
"Locality/text() | CollectorAndNumber/text()
| Institution/text() | Distribution/text()
| Note/text()
"
>
<xsl:value-of select="."/>
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
когда это преобразование применяется к следующему XML-документу:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note></Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
получен желаемый результат:
Locality,CollectorAndNumber,Institution,Distribution
Если требуемые элементы должны быть созданы не в порядке документа (что-то не требуется в вопросе, но поднято Tomalak), для этого все еще довольно легко и элегантно:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="porderedNames"
select="' CollectorAndNumber Locality Distribution Institution Note '"/>
<xsl:template match="/*/*">
<xsl:for-each select=
"*[contains($porderedNames, concat(' ',name(), ' '))]">
<xsl:sort data-type="number"
select="string-length(
substring-before($porderedNames,
concat(' ',name(), ' ')
)
)"/>
<xsl:value-of select="."/>
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Здесь имена искомых элементов и их требуемый порядок указаны в параметре строки $porderedNames
, который содержит список всех желаемых имен, разделенных пробелами.
Когда вышеуказанное преобразование применяется к одному и тому же XML-документу, желаемый результат создается:
CollectorAndNumber,Locality,Distribution,Institution
II. XSLT 2.0:
В XSLT эта задача еще проще (опять же, никакой специальной функции не требуется):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*/*">
<xsl:value-of separator="," select=
"(Locality, CollectorAndNumber,
Institution, Distribution,
Note)[text()]" />
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к одному и тому же XML-документу, получается тот же самый правильный результат:
Locality,CollectorAndNumber,Institution,Distribution
Обратите внимание на, что нужные элементы будут создаваться в любом желаемом порядке, потому что мы используем тип последовательности XPath 2.0 (vs объединение в решении XSLT 1.0), которое по определению содержит элементы в любой желаемый (заданный) порядок.
Ответ 2
Я бы предпочел, чтобы короткий шаблон вызова присоединился к значениям node. Это также работает, если node в середине вашего конкатенированного списка, например. Institution
, отсутствует:
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
Вот краткий пример того, как его использовать:
Пример входного документа:
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>
<Locality>locality1</Locality>
<CollectorAndNumber>collectorAndNumber1</CollectorAndNumber>
<Distribution>distribution1</Distribution>
<Note>note1</Note>
</item>
<item>
<Locality>locality2</Locality>
<CollectorAndNumber>collectorAndNumber2</CollectorAndNumber>
<Institution>institution2</Institution>
<Distribution>distribution2</Distribution>
<Note>note2</Note>
</item>
<item>
<Locality>locality3</Locality>
<CollectorAndNumber>collectorAndNumber3</CollectorAndNumber>
<Institution>institution3</Institution>
<Distribution>distribution3</Distribution>
</item>
</items>
Преобразование XSL:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<summary>
<xsl:apply-templates />
</summary>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:call-template name="join">
<xsl:with-param name="list" select="Locality | CollectorAndNumber | Institution | Distribution | Note" />
<xsl:with-param name="separator" select="','" />
</xsl:call-template>
</item>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Сгенерированный выходной документ:
<?xml version="1.0" encoding="utf-8"?>
<summary>
<item>locality1,collectorAndNumber1,distribution1,note1</item>
<item>locality2,collectorAndNumber2,institution2,distribution2,note2</item>
<item>locality3,collectorAndNumber3,institution3,distribution3</item>
</summary>
NB: Если вы использовали XSLT/XPath 2.0, тогда было бы fn: string-join
fn:string-join**($operand1 as string*, $operand2 as string*) as string
который можно использовать следующим образом:
fn:string-join({Locality, CollectorAndNumber, Distribution, Note}, ",")
Ответ 3
Предположим, что у вас есть что-то вроде следующего входного XML:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note>Note</Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
Затем этот шаблон будет делать это:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:template match="record">
<xsl:variable name="values">
<xsl:apply-templates mode="concat" select="Locality" />
<xsl:apply-templates mode="concat" select="CollectorAndNumber" />
<xsl:apply-templates mode="concat" select="Institution" />
<xsl:apply-templates mode="concat" select="Distribution" />
<xsl:apply-templates mode="concat" select="Note" />
</xsl:variable>
<xsl:value-of select="substring($values, 1, string-length($values) - 1)" />
<xsl:value-of select="' '" /><!-- LF -->
</xsl:template>
<xsl:template match="Locality | CollectorAndNumber | Institution | Distribution | Note" mode="concat">
<xsl:value-of select="." />
<xsl:text>,</xsl:text>
</xsl:template>
</xsl:stylesheet>
Вывод в моей системе:
Locality,CollectorAndNumber,Institution,Distribution,Note
Ответ 4
Думаю, было бы полезно упомянуть,
position() не работает правильно, когда я использую сложный select
который фильтрует некоторые узлы,
в этом случае я придумал этот трюк:
вы можете определить строковую переменную, содержащую значение узлов, разделенных
определенным символом, то с помощью str: tokenize()
вы можете создать полный список node, положение которого отлично работает с ним.
что-то вроде этого:
<!-- Since position() doesn't work as expected(returning node position of current
node list), I got round it by a string variable and tokenizing it in which
absolute position is equal to relative(context) position. -->
<xsl:variable name="measObjLdns" >
<xsl:for-each select="h:measValue[@measObjLdn=$currentMeasObjLdn]/h:measResults" >
<xsl:value-of select="concat(.,'---')"/> <!-- is an optional separator. -->
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="str:tokenize($measObjLdns,'---')" ><!-- Since position() doesn't
work as expected(returning node position of current node list),
I got round it by a string variable and tokenizing it in which
absolute position is equal to relative(context) position. -->
<xsl:value-of select="."></xsl:value-of>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
Ответ 5
У вас нет ценности, которая всегда будет там? Если вы это сделаете, вы можете повернуть его и поставить запятые на все, кроме первого элемента (который будет вашим значением, которое всегда есть).
Ответ 6
Это будет немного беспорядочно, но может сделать трюк, если в вашем примере есть только несколько элементов:
<xsl:if test="Locality != ''">
<xsl:value-of select="Locality"/>
<xsl:if test="CollectorAndNumber != '' or Institution != '' or Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="CollectorAndNumber != ''">
<xsl:value-of select="CollectorAndNumber"/>
<xsl:if test="Institution != '' or Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Institution != ''">
<xsl:value-of select="Institution"/>
<xsl:if test="Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Distribution != ''">
<xsl:value-of select="Distribution"/>
<xsl:if test="Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Note != ''">
<xsl:value-of select="Note"/>
</xsl:if>