2010-05-06 21 views
16

Necesito iterar sobre los caracteres en una cadena para construir una estructura XML.XSLT 1.0: Iterar caracteres sobre una cadena

Actualmente, estoy haciendo esto:

<xsl:template name="verticalize"> 
    <xsl:param name="text">Some text</xsl:param> 
    <xsl:for-each select="tokenize(replace(replace($text,'(.)','$1\\n'),'\\n$',''),'\\n')"> 
     <xsl:element name="para"> 
      <xsl:value-of select="."/> 
     </xsl:element> 
    </xsl:for-each> 
</xsl:template> 

Esto produce algo así como:

<para>S</para> 
<para>o</para> 
<para>m</para> 
<para>e</para> 
<para> </para> 
<para>t</para> 
<para>e</para> 
<para>x</para> 
<para>t</para> 

Esto funciona bien con XPath 2.0. Pero necesito aplicar el mismo tratamiento en un entorno XPath 1.0, donde el método replace() no está disponible.

¿Conoces alguna forma de lograrlo?

+0

excelente pregunta (1). Vea mi respuesta para una solución XSLT 2.0, que también es la más corta hasta ahora. :) –

+0

Vea también mi solución XSLT 1.0. :) –

+0

@Dimitre, si no fuera el más corto, no tendría sentido usar XSLT 2.0 ;-) – Lucero

Respuesta

17
<xsl:template name="letters"> 
    <xsl:param name="text" select="'Some text'" /> 
    <xsl:if test="$text != ''"> 
    <xsl:variable name="letter" select="substring($text, 1, 1)" /> 
    <para><xsl:value-of select="$letter" /></para> 
    <xsl:call-template name="letters"> 
     <xsl:with-param name="text" select="substring-after($text, $letter)" /> 
    </xsl:call-template> 
    </xsl:if> 
</xsl:template> 
+0

Tomalak, eres mi héroe XSLT. – glmxndr

+0

+1 para la solución recursiva de cola –

+0

recursividad de cola es agradable hasta que obtenga un archivo XML gigante que le gusta darle errores stackoverflow. –

8

Si la longitud de la cadena no es grande, puede usar una plantilla recursivamente llamada para lograr esto, pasando el índice del carácter que se procesará como parámetro en la plantilla.

así:

<xsl:template name="verticalize"> 
    <xsl:param name="text">Some text</xsl:param> 
    <xsl:param name="index" select="1" /> 
    <xsl:if test="string-length($text) &gt;= $index"> 
     <xsl:element name="para"> 
      <xsl:value-of select="substring($text, $index, 1)"/> 
     </xsl:element> 
     <xsl:call-template name="verticalize"> 
      <xsl:with-param name="text" select="$text" /> 
      <xsl:with-param name="index" select="$index+1" /> 
     </xsl:call-template> 
    </xsl:if> 
</xsl:template> 

Si la cadena es más que eso, se puede utilizar un enfoque similar pero con un algoritmo de divide y vencerás, para que tenga una profundidad máxima de recursión de log2 (cadena -Longitud), así:

<xsl:template name="verticalize"> 
    <xsl:param name="text">Some text</xsl:param> 
    <xsl:param name="left" select="1" /> 
    <xsl:param name="right" select="string-length($text)" /> 
    <xsl:choose> 
     <xsl:when test="$left = $right"> 
      <xsl:element name="para"> 
       <xsl:value-of select="substring($text, $left, 1)"/> 
      </xsl:element> 
     </xsl:when> 
     <xsl:when test="$left &lt; $right"> 
      <xsl:variable name="middle" select="floor(($left+$right) div 2)" /> 
      <xsl:call-template name="verticalize"> 
       <xsl:with-param name="text" select="$text" /> 
       <xsl:with-param name="left" select="$left" /> 
       <xsl:with-param name="right" select="$middle" /> 
      </xsl:call-template> 
      <xsl:call-template name="verticalize"> 
       <xsl:with-param name="text" select="$text" /> 
       <xsl:with-param name="left" select="$middle+1" /> 
       <xsl:with-param name="right" select="$right" /> 
      </xsl:call-template> 
     </xsl:when> 
    </xsl:choose> 
</xsl:template> 
+0

Muchas gracias por esta respuesta detallada. – glmxndr

+0

De nada, pero ¿por qué no me otorgaste los puntos de respuesta? Estuve 3 minutos antes de Tomalak y ya he publicado la primera muestra ... – Lucero

+0

@Lucero: Publicó una respuesta repetitiva sin código. Tenía mi código listo antes que tu. ;) Pero obtienes +1 de mí para la variante de divide/vencerás. Sin embargo, una sugerencia: recurrir solo al resto de la cadena ('substring-after()') minimiza el uso de la memoria. Su recursión pasa una copia de la cadena completa en cada paso, y esto se suma hasta que la pila se desenrolla. – Tomalak

7

Un XSLT 2.0 Solución:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 

<xsl:variable name="vText" select="'Some Text'"/> 

<xsl:template match="/"> 
    <xsl:for-each select="string-to-codepoints($vText)"> 
     <para><xsl:sequence select="codepoints-to-string(.)"/></para> 
    </xsl:for-each> 
</xsl:template> 
</xsl:stylesheet> 

Para aquellos de ustedes, XSLT 2.0/2.0 XPath aprendizaje, hacer la nota:

  1. El uso de la norma XPath 2.0 Funciones string-to-codepoints() y codepoints-to-string().

  2. En XSLT 2.0 el valor del atributo select de <xsl:for-each> puede ser una secuencia de elementos, no solo nodos.

+0

Buena solución. Sin embargo, no funcionará como se espera cuando se combinen caracteres para representar acentos, etc. .: http://en.wikipedia.org/wiki/Combining_character – Lucero

+0

@Lucero: Eso es interesante. ¿Podría darme un ejemplo de "carácter combinado" para que yo lo entienda? Además, me parece que ninguna de las otras soluciones funcionaría tampoco en este caso. –

+0

@Dimitre, para ejemplos, consulte Wikipedia: http://en.wikipedia.org/wiki/Unicode_normalization No estoy seguro de si las otras soluciones funcionarían o no; Esperaría que las funciones de cadena funcionaran en caracteres compuestos, ya que todo lo demás rompería los caracteres únicos (textuales). Tendría que verificar las especificaciones XPath/XSLT sobre cómo funcionan. – Lucero

2

Un XSLT 1.0 Solución usando FXSL

El FXSL library ofrece una serie de funciones genéricas para el procesamiento de la lista. Casi todos ellos tienen un análogo para operar en cadenas (con respecto a una cadena como una lista de caracteres).

Aquí se muestra un ejemplo usando el str-foldl función/plantilla:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:dvc-foldl-func="dvc-foldl-func" 
exclude-result-prefixes="xsl dvc-foldl-func" 
> 

    <xsl:import href="dvc-str-foldl.xsl"/> 

    <dvc-foldl-func:dvc-foldl-func/> 
    <xsl:variable name="vFoldlFun" select="document('')/*/dvc-foldl-func:*[1]"/> 
    <xsl:output encoding="UTF-8" omit-xml-declaration="yes"/> 

    <xsl:template match="/"> 

     <xsl:call-template name="dvc-str-foldl"> 
     <xsl:with-param name="pFunc" select="$vFoldlFun"/> 
     <xsl:with-param name="pStr" select="123456789"/> 
     <xsl:with-param name="pA0" select="0"/> 
     </xsl:call-template> 
    </xsl:template> 

    <xsl:template match="dvc-foldl-func:*"> 
     <xsl:param name="arg1" select="0"/> 
     <xsl:param name="arg2" select="0"/> 

     <xsl:value-of select="$arg1 + $arg2"/> 
    </xsl:template> 

</xsl:stylesheet> 

Esta transformación calcula la suma de los caracteres en la cadena pasado como parámetro $pStr y produce el resultado correcto:

Y usando la plantilla/función str-map tenemos la siguiente fácil d solución a corto:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:testmap="testmap" 
exclude-result-prefixes="xsl testmap" 
> 
    <xsl:import href="str-dvc-map.xsl"/> 

    <!-- to be applied on any xml source --> 

    <testmap:testmap/> 

    <xsl:output omit-xml-declaration="yes" indent="yes"/> 

    <xsl:template match="/"> 
    <xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/> 
    <xsl:call-template name="str-map"> 
     <xsl:with-param name="pFun" select="$vTestMap"/> 
     <xsl:with-param name="pStr" select="'Some Text'"/> 
    </xsl:call-template> 
    </xsl:template> 

    <xsl:template name="split" match="*[namespace-uri() = 'testmap']"> 
     <xsl:param name="arg1"/> 

     <para><xsl:value-of select="$arg1"/></para> 
    </xsl:template> 

</xsl:stylesheet> 

cuando se aplica sobre cualquier archivo XML (no se utiliza), el, resultado correcto querido se produce:

<para>S</para> 
<para>o</para> 
<para>m</para> 
<para>e</para> 
<para> </para> 
<para>T</para> 
<para>e</para> 
<para>x</para> 
<para>t</para>