Reemplazo de cadena XSLT

85

Realmente no sé XSL pero necesito arreglar este código, lo he reducido para hacerlo más simple.
Estoy recibiendo este error

Función XSLT / XPath no válida

en esta linea

<xsl:variable name="text" select="replace($text,'a','b')"/>

Este es el XSL

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:inm="http://www.inmagic.com/webpublisher/query" version="1.0">
    <xsl:output method="text" encoding="UTF-8" />

    <xsl:preserve-space elements="*" />
    <xsl:template match="text()" />

    <xsl:template match="mos">
        <xsl:apply-templates />

        <xsl:for-each select="mosObj">
          'Notes or subject' 
           <xsl:call-template
                name="rem-html">
                <xsl:with-param name="text" select="SBS_ABSTRACT" />
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="rem-html">
        <xsl:param name="text" />
        <xsl:variable name="text" select="replace($text, 'a', 'b')" />
    </xsl:template>
</xsl:stylesheet>

¿Alguien puede decirme qué tiene de malo?

Aximili
fuente
Tenga en cuenta que la replace()función está disponible desde XPath 2.0 (y por lo tanto XSLT 2.0) en adelante y admite reemplazos de expresiones regulares.
Abel

Respuestas:

147

replace no está disponible para XSLT 1.0.

Codesling tiene una plantilla para reemplazar cadenas que puede usar como sustituto de la función:

<xsl:template name="string-replace-all">
    <xsl:param name="text" />
    <xsl:param name="replace" />
    <xsl:param name="by" />
    <xsl:choose>
        <xsl:when test="$text = '' or $replace = ''or not($replace)" >
            <!-- Prevent this routine from hanging -->
            <xsl:value-of select="$text" />
        </xsl:when>
        <xsl:when test="contains($text, $replace)">
            <xsl:value-of select="substring-before($text,$replace)" />
            <xsl:value-of select="$by" />
            <xsl:call-template name="string-replace-all">
                <xsl:with-param name="text" select="substring-after($text,$replace)" />
                <xsl:with-param name="replace" select="$replace" />
                <xsl:with-param name="by" select="$by" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

invocado como:

<xsl:variable name="newtext">
    <xsl:call-template name="string-replace-all">
        <xsl:with-param name="text" select="$text" />
        <xsl:with-param name="replace" select="a" />
        <xsl:with-param name="by" select="b" />
    </xsl:call-template>
</xsl:variable>

Por otro lado, si literalmente solo necesita reemplazar un carácter por otro, puede llamar al translateque tenga una firma similar. Algo como esto debería funcionar bien:

<xsl:variable name="newtext" select="translate($text,'a','b')"/>

Además, tenga en cuenta que en este ejemplo, cambié el nombre de la variable a "nuevo texto", en XSLT las variables son inmutables, por lo que no puede hacer el equivalente $foo = $fooa lo que tenía en su código original.

Mark Elliot
fuente
Gracias Mark, pero ahora recibo este error: se llamó a una función de extensión XPath desconocida
Aximili
@aximili, lo siento, confundí XSLT 1.0 y 2.0, lo edité ... debería estar listo ahora.
Mark Elliot
19
¡Esta respuesta es incorrecta! La función de reemplazo en XSLT reemplaza los CARACTERES ÚNICOS correspondientes, ¡no las cadenas completas! Vea por ejemplo aquí: w3schools.com/xpath/xpath_functions.asp
Jakub
12
@Jakub Estás pensando translate, no replace. La replacefunción en XPath 2.0 trata su segundo argumento como una expresión regular y reemplaza todas las coincidencias de esa expresión con la cadena de reemplazo especificada (que puede incluir $nreferencias a la captura de grupos en la expresión regular). La translatefunción (en 1.0 y 2.0) es la que hace reemplazos de un solo carácter por un solo carácter.
Ian Roberts
6
¿No debería la cuarta línea en el uso del ejemplo estar <xsl:with-param name="replace" select="'a'" />con comillas alrededor de la a?
DJL
37

Aquí está la función XSLT que funcionará de manera similar a la función String.Replace () de C #.

Esta plantilla tiene los 3 parámetros de la siguiente manera

texto : - tu cadena principal

reemplazar : - la cadena que desea reemplazar

por : - la cadena que responderá con una nueva cadena

A continuación se muestra la plantilla

<xsl:template name="string-replace-all">
  <xsl:param name="text" />
  <xsl:param name="replace" />
  <xsl:param name="by" />
  <xsl:choose>
    <xsl:when test="contains($text, $replace)">
      <xsl:value-of select="substring-before($text,$replace)" />
      <xsl:value-of select="$by" />
      <xsl:call-template name="string-replace-all">
        <xsl:with-param name="text" select="substring-after($text,$replace)" />
        <xsl:with-param name="replace" select="$replace" />
        <xsl:with-param name="by" select="$by" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$text" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

El siguiente ejemplo muestra cómo llamarlo

<xsl:variable name="myVariable ">
  <xsl:call-template name="string-replace-all">
    <xsl:with-param name="text" select="'This is a {old} text'" />
    <xsl:with-param name="replace" select="'{old}'" />
    <xsl:with-param name="by" select="'New'" />
  </xsl:call-template>
</xsl:variable>

También puede consultar la siguiente URL para obtener más detalles.

Optimus
fuente
1
Usando xslt 1.0 Esta publicación / plantilla funcionó para mí, mientras que la de Mark Elliot no.
HostMyBus
12

Nota: En caso de que desee utilizar el algoritmo ya mencionado para los casos en los que necesite reemplazar una gran cantidad de instancias en la cadena de origen (por ejemplo, nuevas líneas en texto largo), existe una alta probabilidad de que termine StackOverflowExceptiondebido al recursivo llamada.

Resolví este problema gracias a la incrustación de tipo Java incorporada de Xalan (no vi cómo hacerlo en Saxon ):

<xsl:stylesheet version="1.0" exclude-result-prefixes="xalan str"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xalan="http://xml.apache.org/xalan"
                xmlns:str="xalan://java.lang.String"
        >
...
<xsl:value-of select="str:replaceAll(
    str:new(text()),
    $search_string,
    $replace_string)"/>
...
</xsl:stylesheet>
Milán Aleksić
fuente
Lo siento si estoy siendo tonto, pero me sale:Cannot find a script or an extension object associated with namespace 'xalan://java.lang.String'.
Ian Grainger
¿Cuál es su motor XSLT?
Milan Aleksić
3
Mi comentario fue para Xalan ( xml.apache.org/xalan-j ), el motor más popular de Java XSLT 1.0 , que admite el mapeo directo a los tipos disponibles dentro de la ruta de clases Java disponible; no se puede aplicar mi solución para la pila .Net
Milan Aleksić
@IanGrainger, puede usarlo con .NET agregando un <msxsl:script>bloque, que puede llamar a cualquier método .NET, biblioteca, etc. Aunque .NET también admite las funciones de extensión EXSLT, por lo que no es necesario.
Abel
exslt también es compatible con libxslt y, por lo tanto, en todos los descendientes xsltproc, etc.
Alain Pannetier
7

Puede usar el siguiente código cuando su procesador se ejecuta en .NET o usa MSXML (a diferencia de los procesadores nativos basados ​​en Java u otros). Utiliza msxsl:script.

Asegúrese de agregar el espacio xmlns:msxsl="urn:schemas-microsoft-com:xslt"de nombres a su raíz xsl:stylesheeto xsl:transformelemento.

Además, vincula outleta cualquier espacio de nombres que quieras, por ejemplo xmlns:outlet = "http://my.functions".

<msxsl:script implements-prefix="outlet" language="javascript">
function replace_str(str_text,str_replace,str_by)
{
     return str_text.replace(str_replace,str_by);
}
</msxsl:script>


<xsl:variable name="newtext" select="outlet:replace_str(string(@oldstring),'me','you')" />
John Jin
fuente
Lo siento si estoy siendo tonto, pero obtengo prefix outlet is not definedo 'xsl:script' cannot be a child of the 'xsl:stylesheet' element.si cambio msxsl por mi prefijo. Supongo que esto es algo de magia XSLT específica de Microsoft.
Ian Grainger
1
@IanGrainger, no lo es xsl:script, pero msxsl:script, y tiene un espacio de nombres diferente (he actualizado la respuesta de John).
Abel
1

Sigo dando esta respuesta. Pero ninguno de ellos enumera la solución más sencilla para xsltproc (y probablemente la mayoría de los procesadores XSLT 1.0):

  1. Agregue el nombre de las cadenas exslt a la hoja de estilo, es decir:
<xsl:stylesheet
  version="1.0"
  xmlns:str="http://exslt.org/strings"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  1. Entonces úsalo como:
<xsl:value-of select="str:replace(., ' ', '')"/>
Berend de Boer
fuente
1
El xsltproc en mi computadora (macOS 10.13) NO admite la str:replace()función. Tampoco lo hace ninguno de los otros procesadores XSLT 1.0 principales: Xalan, Saxon 6.5 y Microsoft.
michael.hor257k
0

La rutina es bastante buena, sin embargo, hace que mi aplicación se cuelgue, por lo que necesitaba agregar el caso:

  <xsl:when test="$text = '' or $replace = ''or not($replace)" >
    <xsl:value-of select="$text" />
    <!-- Prevent thsi routine from hanging -->
  </xsl:when>

antes de que la función se llame de forma recursiva.

Obtuve la respuesta desde aquí: cuando la prueba se cuelga en un bucle infinito

¡Gracias!

Chesare
fuente