transformador - Cadena concat XSLT, eliminar la última coma
xslt editor (6)
¿No tienes un valor que siempre estará ahí? Si lo haces, puedes darle la vuelta y poner comas delante de todo aparte del primer elemento (que sería tu valor que siempre está ahí).
Necesito construir una cadena usando XSLT y separar cada cadena con una coma, pero no incluir una coma después de la última cadena. En mi ejemplo a continuación, tendré una coma final si tengo un nodo de Distribución y no un nodo Nota, por ejemplo. No sé de todos modos para construir una cadena como una variable y luego truncar el último carácter en XSLT. También esto está usando el motor Microsoft XSLT.
Mi cadena =
<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>
[Hombre, debe haber una mejor manera de ingresar a este cuadro de texto de pregunta :(]
Creo que podría ser útil mencionar que position () no funciona bien cuando uso una selección complicada que filtra algunos nodos, en ese caso surgió cuál es este truco:
puede definir una variable de cadena que contenga el valor de los nodos, separados por un carácter específico, y luego mediante str: tokenize () puede crear una lista de nodos completa cuya posición funciona bien con ella.
algo como esto:
<!-- 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>
Esto sería un poco complicado, pero podría hacer el truco si solo hay unos pocos elementos como en su ejemplo:
<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>
Preferiría una plantilla de llamada corta para unir los valores de los nodos. Esto también funciona si falta un nodo en el medio de su lista concatenada, por ejemplo, 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>
Aquí hay un breve ejemplo de cómo usarlo:
Documento de entrada de muestra:
<?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>
Transformación 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>
Documento de salida generado:
<?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: si estuvieras usando XSLT / XPath 2.0, entonces habría fn:string-join
fn:string-join**($operand1 as string*, $operand2 as string*) as string
que podría usarse de la siguiente manera:
fn:string-join({Locality, CollectorAndNumber, Distribution, Note}, ",")
Supongamos que tiene algo así como el siguiente XML de entrada:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note>Note</Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
Entonces esta plantilla lo haría:
<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>
Salida en mi sistema:
Locality,CollectorAndNumber,Institution,Distribution,Note
Esto es muy fácil de lograr con XSLT ( no es necesario capturar los resultados en una variable, o usar plantillas especiales con nombre ):
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>
cuando esta transformación se aplica en el siguiente documento XML:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note></Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
el resultado deseado es producido :
Locality,CollectorAndNumber,Institution,Distribution
Si los elementos buscados no se deben producir en orden de documentos (algo no requerido en la pregunta, pero planteado por Tomalak), sigue siendo bastante fácil y elegante lograr esto:
<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>
Aquí los nombres de los elementos deseados y su orden deseada se proporcionan en el parámetro de cadena $porderedNames
, que contiene una lista separada por espacios de todos los nombres deseados .
Cuando la transformación anterior se aplica en el mismo documento XML, se produce el resultado deseado :
CollectorAndNumber,Locality,Distribution,Institution
II. XSLT 2.0 :
En XSLT, esta tarea es incluso más simple ( nuevamente, no es necesaria ninguna función especial ):
<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>
Cuando esta transformación se aplica en el mismo documento XML, se produce el mismo resultado correcto :
Locality,CollectorAndNumber,Institution,Distribution
Tenga en cuenta que los elementos deseados se producirán en el orden deseado, porque estamos utilizando el tipo de secuencia XPath 2.0 (frente a la unión en la solución XSLT 1.0), que por definición contiene elementos en el orden deseado (especificado).