2009-07-21 35 views
21

¿Cómo puedo convertir el siguiente XML a un texto escapado utilizando XSLT?Convertir XML a texto escapado en XSLT

Fuente:

<?xml version="1.0" encoding="utf-8"?> 
<abc> 
    <def ghi="jkl"> 
    mnop 
    </def> 
</abc> 

Salida:

<TestElement>&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;abc&gt;&lt;def ghi="jkl"&gt; 
    mnop 
    &lt;/def&gt;&lt;/abc&gt;</TestElement> 

Actualmente, estoy intentando lo siguiente XSLT y no parece que funcione correctamente:

<?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" encoding="utf-8" /> 
    <xsl:template match="/"> 
    <xsl:variable name="testVar"> 
     <xsl:copy> 
     <xsl:apply-templates select="@* | node()"/> 
     </xsl:copy> 
    </xsl:variable> 

    <TestElement> 
     <xsl:value-of select="$testVar"/> 
    </TestElement> 
    </xsl:template> 
</xsl:stylesheet> 

salida de La instrucción XSLT de .NET XslCompiledTransform sale como sigue:

<?xml version="1.0" encoding="utf-8"?><TestElement> 

    mnop 

</TestElement> 
+1

¿Exactamente cómo no funciona correctamente? –

+0

Agregué el resultado del XSLT según el comentario de John. –

Respuesta

33

Su código funciona de la manera en que lo hace porque xsl:value-of recupera el string-value del conjunto de nodos.

para hacer lo que quiere, me temo que vas a tener que codificar explícitamente:

<xsl:template match="/"> 
     <TestElement> 
      <xsl:apply-templates mode="escape"/> 
     </TestElement> 
    </xsl:template> 

    <xsl:template match="*" mode="escape"> 
     <!-- Begin opening tag --> 
     <xsl:text>&lt;</xsl:text> 
     <xsl:value-of select="name()"/> 

     <!-- Namespaces --> 
     <xsl:for-each select="namespace::*"> 
      <xsl:text> xmlns</xsl:text> 
      <xsl:if test="name() != ''"> 
       <xsl:text>:</xsl:text> 
       <xsl:value-of select="name()"/> 
      </xsl:if> 
      <xsl:text>='</xsl:text> 
      <xsl:call-template name="escape-xml"> 
       <xsl:with-param name="text" select="."/> 
      </xsl:call-template> 
      <xsl:text>'</xsl:text> 
     </xsl:for-each> 

     <!-- Attributes --> 
     <xsl:for-each select="@*"> 
      <xsl:text> </xsl:text> 
      <xsl:value-of select="name()"/> 
      <xsl:text>='</xsl:text> 
      <xsl:call-template name="escape-xml"> 
       <xsl:with-param name="text" select="."/> 
      </xsl:call-template> 
      <xsl:text>'</xsl:text> 
     </xsl:for-each> 

     <!-- End opening tag --> 
     <xsl:text>&gt;</xsl:text> 

     <!-- Content (child elements, text nodes, and PIs) --> 
     <xsl:apply-templates select="node()" mode="escape" /> 

     <!-- Closing tag --> 
     <xsl:text>&lt;/</xsl:text> 
     <xsl:value-of select="name()"/> 
     <xsl:text>&gt;</xsl:text> 
    </xsl:template> 

    <xsl:template match="text()" mode="escape"> 
     <xsl:call-template name="escape-xml"> 
      <xsl:with-param name="text" select="."/> 
     </xsl:call-template> 
    </xsl:template> 

    <xsl:template match="processing-instruction()" mode="escape"> 
     <xsl:text>&lt;?</xsl:text> 
     <xsl:value-of select="name()"/> 
     <xsl:text> </xsl:text> 
     <xsl:call-template name="escape-xml"> 
      <xsl:with-param name="text" select="."/> 
     </xsl:call-template> 
     <xsl:text>?&gt;</xsl:text> 
    </xsl:template> 

    <xsl:template name="escape-xml"> 
     <xsl:param name="text"/> 
     <xsl:if test="$text != ''"> 
      <xsl:variable name="head" select="substring($text, 1, 1)"/> 
      <xsl:variable name="tail" select="substring($text, 2)"/> 
      <xsl:choose> 
       <xsl:when test="$head = '&amp;'">&amp;amp;</xsl:when> 
       <xsl:when test="$head = '&lt;'">&amp;lt;</xsl:when> 
       <xsl:when test="$head = '&gt;'">&amp;gt;</xsl:when> 
       <xsl:when test="$head = '&quot;'">&amp;quot;</xsl:when> 
       <xsl:when test="$head = &quot;&apos;&quot;">&amp;apos;</xsl:when> 
       <xsl:otherwise><xsl:value-of select="$head"/></xsl:otherwise> 
      </xsl:choose> 
      <xsl:call-template name="escape-xml"> 
       <xsl:with-param name="text" select="$tail"/> 
      </xsl:call-template> 
     </xsl:if> 
    </xsl:template> 

Tenga en cuenta que esta solución hace caso omiso de los nodos de comentario, e inserta nodos espacio de nombres innecesarios (como namespace:: eje incluirán todos los nodos heredados del padre). Con respecto a espacios de nombres, sin embargo, el XML citado resultante será semánticamente equivalente al ejemplo que usted proporcionó en su respuesta (ya que esas redeclaraciones repetidas realmente no cambian nada).

Además, esto no escapará a la declaración <?xml ... ?>, simplemente porque no está presente en el modelo de datos XPath 1.0 (no es una instrucción de procesamiento). Si realmente lo necesita en la salida, tendrá que insertarlo manualmente (y asegúrese de que la codificación que especifica sea consistente con la codificación de serialización de su procesador XSLT).

+0

increíble !!!!! +1 –

-1

Por qué no puedes simplemente ejecutar

<xsl:template match="/"> 
    <TestElement> 
    <xsl:copy-of select="." /> 
    </TestElement> 
</xsl:template> 
+2

Porque producirá un árbol XML, no la cadena que representa el árbol XML (como se solicitó el OP). – bortzmeyer

0

¿Se necesidad utilizar XSLT? Porque, por las razones explicadas por Pavel Minaev, sería mucho más simple usar otra herramienta. Un ejemplo con xmlstartlet:

 
% xmlstarlet escape 
<?xml version="1.0" encoding="utf-8"?> 
<abc> 
    <def ghi="jkl"> 
    mnop 
    </def> 
</abc> 
[Control-D] 
&lt;?xml version="1.0" encoding="utf-8"?&gt; 
&lt;abc&gt; 
    &lt;def ghi="jkl"&gt; 
    mnop 
    &lt;/def&gt; 
&lt;/abc&gt; 
+0

Lamentablemente, esto está fuera de lo que estoy pidiendo. Últimamente, he visto servicios web que toman una cadena como parámetro. Lo que es peor es que el parámetro de cadena toma XML como entrada. Por una característica requerida, necesito transformar un documento XML a un sobre SOAP. El problema es que necesito convertir el documento XML a un texto escapado para pasarlo como un parámetro de cadena del sobre SOAP (dado que todo lo demás es estático). Por lo tanto, el problema que pregunté surge si no está utilizando otra biblioteca de clase proxy en el medio. –

0

Si usted tiene acceso a ella, yo recomendaría la extensión Saxon serialize. Hace exactamente lo que quieres que haga. Si no desea hacer eso, tendrá que insertar manualmente las referencias de entidad a medida que crea el documento. Sería frágil, pero funcionaría para la mayoría de los documentos:

<xsl:template match="/"> 
    <TestElement> 
     <xsl:apply-templates/> 
    </TestElement> 
</xsl:template> 
<xsl:template match="*"> 
    <xsl:text>&lt;</xsl:text> 
    <xsl:value-of select="name()"/> 
    <xsl:apply-templates select="@*"/> 
    <xsl:text>&gt;</xsl:text> 
    <xsl:apply-templates select="node()"/> 
    <xsl:text>&lt;/</xsl:text> 
    <xsl:value-of select="name()"/> 
    <xsl:text>&gt;</xsl:text> 
</xsl:template> 
<xsl:template match="@*"> 
    <xsl:text>&#32;</xsl:text> 
    <xsl:value-of select="name()"/> 
    <xsl:text>="</xsl:text> 
    <xsl:value-of select="."/> 
    <xsl:text>"</xsl:text> 
</xsl:template> 
<xsl:template match="text()"> 
    <xsl:value-of select="."/> 
</xsl:template> 

Sobre todo, esto probablemente se romperá si sus atributos tienen el carácter de comillas dobles. Es mejor usar saxon, o usar una extensión escrita por el usuario que use un serializador adecuado si no puede hacerlo.

16

en lugar de escapar, puede agregar el texto dentro de una sección CDATA. El texto dentro de una sección CDATA será ignorado por el analizador, similar a si se escapó.

tu ejemplo sería el siguiente

<TestElement> 
<![CDATA[ 
<abc> 
    <def ghi="jkl"> 
    mnop 
    </def> 
</abc> 
]]> 
</TestElement> 

usando siguiente fragmento de XSLT:

<xsl:text disable-output-escaping="yes">&lt;![CDATA[</xsl:text> 
<xsl:copy-of select="/"/> 
<xsl:text disable-output-escaping="yes">]]</xsl:text> 
<xsl:text disable-output-escaping="yes">&gt;</xsl:text> 
+0

+1 por ayudarme con mi problema (aparte de esta pregunta). –

+0

Esto fue muy útil para mí también. Parece un truco mejor que el serializador Saxon. –

+0

Aunque no funcionó para mí con salida HTML. El documento se ve bien, pero de alguna manera el navegador se niega a mostrar el texto. Consigo ]]> Pero el navegador sólo muestra "]]>". Estoy asumiendo que esto podría ser un problema de HTML. ¿Alguien puede confirmar eso? –

4

Cualquier persona que se preocupa por la concesión de licencias ambigüedad cuando la reutilización de fragmentos de código de desbordamiento de pila puede estar interesado en los siguientes 3 -clause código con licencia BSD, que parece hacer lo que pide el póster original:

http://lenzconsulting.com/xml-to-string/

1

Puede impedir que los nodos espacio de nombres adicionales mediante la adición de una prueba en la salida del espacio de nombres:


<xsl:variable name="curnode" select="."/> 
    <xsl:for-each select="namespace::*"> 
     <xsl:variable name="nsuri" select="."/> 
     <xsl:if test="$curnode/descendant-or-self::*[namespace-uri()=$nsuri]"> 
     ... 
3

he tratado de poner en práctica la respuesta proporcionada por Pavel Minaev y quiere señalar que esto es muy peligroso para las grandes cadenas ya que cada carácter de la cadena de entrada se repite de manera individual, lo que hace que la profundidad de recursión se agote rápidamente. Intenté ejecutarlo en unas pocas líneas de texto y causó un desbordamiento de la pila (lol).

En su lugar, utilizo una plantilla que no necesita examinar cada carácter individual, sino que saldrá el texto hasta que encuentre una cadena que deba ser reemplazada. Esto puede ser usado para escapar caracteres:

<xsl:template name="Search-And-Replace"> 
    <xsl:param name="Input-String"/> 
    <xsl:param name="Search-String"/> 
    <xsl:param name="Replace-String"/> 
    <xsl:choose> 
     <xsl:when test="$Search-String and contains($Input-String, $Search-String)"> 
      <xsl:value-of select="substring-before($Input-String, $Search-String)"/> 
      <xsl:value-of select="$Replace-String"/>   
      <xsl:call-template name="Search-And-Replace"> 
       <xsl:with-param name="Input-String" select="substring-after($Input-String, $Search-String)"/> 
       <xsl:with-param name="Search-String" select="$Search-String"/> 
       <xsl:with-param name="Replace-String" select="$Replace-String"/> 
      </xsl:call-template> 
     </xsl:when> 
     <xsl:otherwise> 
      <xsl:value-of select="$Input-String"/> 
     </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

Entonces es sólo una cuestión de llamar a esa plantilla para el carbón que desea escapar ..

<xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="Hi I am a string &amp; I am awesome"/> 
      <xsl:with-param name="Search-String" select="'&amp;'"/> 
      <xsl:with-param name="Replace-String" select="'&amp;amp;'"/> 
    </xsl:call-template> 

Con el fin de escapar varios caracteres en una cadena, utilicé una plantilla de contenedor que usa variables ...

<xsl:template name="EscapeText"> 
    <xsl:param name="text" /> 

    <xsl:variable name="a"> 
    <xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="$text"/> 
      <xsl:with-param name="Search-String" select="'&amp;'"/> 
      <xsl:with-param name="Replace-String" select="'&amp;amp;'"/> 
     </xsl:call-template>    
    </xsl:variable> 

    <xsl:variable name="b">  
     <xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="$a"/> 
      <xsl:with-param name="Search-String" select="'&quot;'"/> 
      <xsl:with-param name="Replace-String" select="'&amp;quot;'"/> 
     </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="c">  
     <xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="$b"/> 
      <xsl:with-param name="Search-String">&apos;</xsl:with-param> 
      <xsl:with-param name="Replace-String" select="'&amp;apos;'"/> 
     </xsl:call-template> 
    </xsl:variable>   

    <xsl:variable name="d">  
     <xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="$c"/> 
      <xsl:with-param name="Search-String" select="'&gt;'"/> 
      <xsl:with-param name="Replace-String" select="'&amp;gt;'"/> 
     </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="e"> 
     <xsl:call-template name="Search-And-Replace"> 
      <xsl:with-param name="Input-String" select="$d"/> 
      <xsl:with-param name="Search-String" select="'&lt;'"/> 
      <xsl:with-param name="Replace-String" select="'&amp;lt;'"/> 
     </xsl:call-template> 
    </xsl:variable>  
    <!--this is the final output--> 
    <xsl:value-of select="$e"/>  
</xsl:template> 

Esto demostró ser mucho más seguro para cadenas grandes ya que ya no tiene que recurrir para cada carácter individual en la cadena de entrada.

+0

Mis largas cadenas rompieron la solución de recursión de caracteres individuales también.Esta solución funcionó bien para mí, pero tuve que agregar un 'disable-output-escaping' al resultado final:' '. ¡Gracias! – twamley