2008-10-08 22 views
35

Estoy tratando de convertir un archivo XML en el marcado utilizado por dokuwiki, usando XSLT. En realidad, esto funciona hasta cierto punto, pero la sangría en el archivo XSL se está insertando en los resultados. Por el momento, tengo dos opciones: abandonar esta cosa XSLT por completo, y buscar otra forma de convertir de XML a marcado dokuwiki, o eliminar aproximadamente el 95% de los espacios en blanco del archivo XSL, convirtiéndolo casi ilegible y una pesadilla de mantenimiento.Convertir XML a texto sin formato: ¿cómo debo ignorar/manejar espacios en blanco en el XSLT?

¿Hay alguna manera de mantener la sangría en el archivo XSL sin pasar todo ese espacio en blanco en el documento final?

Antecedentes: estoy migrando una herramienta autodoc de páginas HTML estáticas a dokuwiki, por lo que el equipo de aplicaciones puede documentar aún más la API desarrollada por el equipo servidor cada vez que el equipo de aplicaciones se encuentra con un código mal documentado. La lógica es tener una sección de cada página reservada para la herramienta autodoc y permitir comentarios en cualquier lugar fuera de este bloque. Estoy usando XSLT porque ya tenemos el archivo XSL para convertir de XML a XHTML, y asumo que será más rápido reescribir el XSL que rodar mi propia solución desde cero.

Editar: Ah, bien, tonto, descuidé el atributo de sangría. (Otra nota de fondo: soy nuevo en XSLT.) Por otro lado, todavía tengo que ocuparme de las nuevas líneas. Dokuwiki utiliza tuberías para diferenciar entre columnas de la tabla, lo que significa que todos los datos en una línea de la tabla deben estar en una línea. ¿Hay alguna manera de suprimir las nuevas líneas de salida (solo ocasionalmente), entonces puedo hacer una lógica bastante compleja para cada celda de la tabla en un fasion algo legible?

Respuesta

75

Hay tres razones para obtener un espacio en blanco no deseado en el resultado de una transformación XSLT:

  1. espacios en blanco que viene de entre los nodos en el documento fuente
  2. espacios en blanco que viene de dentro de los nodos en el documento fuente
  3. espacios en blanco que viene de la hoja de estilo

voy a hablar de los tres porque puede ser difícil saber de dónde viene el espacio en blanco, por lo que es posible que necesite usar varias estrategias.

Para hacer frente a los espacios en blanco que se encuentra entre los nodos en el documento de origen, se debe utilizar <xsl:strip-space> que se deben eliminar cualquier espacio en blanco que aparece entre dos nodos, y luego usar <xsl:preserve-space> para preservar el espacio en blanco significativo que pudiera aparecer dentro de contenido mixto.Por ejemplo, si el documento de origen se ve así:

<ul> 
    <li>This is an <strong>important</strong> <em>point</em></li> 
</ul> 

, entonces tendrá que pasar por alto el espacio en blanco entre el <ul> y la <li> y entre la </li> y la </ul>, que no es significativo, pero conservar el espacio en blanco entre el <strong> y <em> elementos, que es significativo (de lo contrario, obtendría "Este es un ** punto *** importante *"). Para ello el uso

<xsl:strip-space elements="*" /> 
<xsl:preserve-space elements="li" /> 

El atributo elements en <xsl:preserve-space> debe básicamente una lista de todos los elementos del documento que se han mezclado el contenido.

Aparte: el uso de <xsl:strip-space> también reduce el tamaño del árbol de fuentes en la memoria, y hace que su hoja de estilo más eficiente, por lo que vale la pena hacer, incluso si usted no tiene problemas de espacio en blanco de este tipo.

Para abordar el espacio en blanco que aparece dentro de los nodos en su documento de origen, debe usar normalize-space(). Por ejemplo, si usted tiene:

<dt> 
    a definition 
</dt> 

y usted puede estar seguro de que el elemento <dt> no dará ningún elemento que desee hacer algo con, entonces usted puede hacer:

<xsl:template match="dt"> 
    ... 
    <xsl:value-of select="normalize-space(.)" /> 
    ... 
</xsl:template> 

El los espacios en blanco iniciales y finales se eliminarán del valor del elemento <dt> y obtendrá la cadena "a definition".

Para hacer frente a los espacios en blanco que viene de la hoja de estilo, que es quizás el que usted está experimentando, es cuando tiene texto dentro de una plantilla como esta:

<xsl:template match="name"> 
    Name: 
    <xsl:value-of select="." /> 
</xsl:template> 

hojas de estilo XSLT se analizan de la misma manera como el documentos de origen que procesan, por lo que el XSLT anterior se interpreta como un árbol que contiene un elemento <xsl:template> con un atributo match cuyo primer hijo es un nodo de texto y cuyo segundo hijo es un elemento <xsl:value-of> con un atributo select. El nodo de texto tiene espacios en blanco iniciales y finales (incluidos saltos de línea); dado que es texto literal en la hoja de estilo, se copia literalmente en el resultado, con todos los espacios en blanco iniciales y finales.

Pero algunos espacios en blanco en hojas de estilos XSLT se eliminan automáticamente, es decir, aquellos entre los nodos. No obtiene un salto de línea en su resultado porque hay un salto de línea entre el <xsl:value-of> y el cierre del <xsl:template>.

Para obtener únicamente el texto que desee en el resultado, utilice el elemento <xsl:text> así:

<xsl:template match="name"> 
    <xsl:text>Name: </xsl:text> 
    <xsl:value-of select="." /> 
</xsl:template> 

El procesador XSLT ignorará los saltos de línea y sangría que aparecen entre nodos, y sólo la salida del texto dentro de el elemento <xsl:text>.

+0

¡Esto fue extremadamente útil! Gracias. – Black

+0

que de hecho fue útil, pero estoy confundido por el uso de la frase "entre nodos". ¿No es cierto que todos los espacios en blanco están contenidos en los nodos de texto? ¿Qué quieres decir con "entre nodos"? Si no hubiera reconocido su nombre, hubiera asumido que necesitaba una conferencia sobre la estructura del documento XML. – LarsH

+0

¡Buen artículo, gracias! Pero estrictamente hablando, estás usando el término 'nodo' en el que realmente quieres decir 'elemento'. – rustyx

4

¿Está utilizando indent = "no" en la etiqueta de salida?

<xsl:output method="text" indent="no" /> 

Además, si usted está utilizando XSL: valor de puede utilizar el disable-output-escaping = "sí" para ayudar con algunos problemas de espacio en blanco.

+4

La mayor parte del tiempo, usar 'disable-output-escaping' es la forma incorrecta de hacer las cosas. Solo está ahí para situaciones muy restringidas. Defender d-o-e de una manera tan general a alguien que no sabe mejor es probablemente más dañino que útil. Ver http://www.dpawson.co.uk/xsl/sect2/N2215.html#d3702e223 – LarsH

0

En cuanto a la edición de nuevas líneas, puede utilizar esta plantilla para sustituir de forma recursiva una cadena dentro de otra cadena, y se puede utilizar para saltos de línea:

<xsl:template name="replace.string.section"> 
    <xsl:param name="in.string"/> 
    <xsl:param name="in.characters"/> 
    <xsl:param name="out.characters"/> 
    <xsl:choose> 
    <xsl:when test="contains($in.string,$in.characters)"> 
     <xsl:value-of select="concat(substring-before($in.string,$in.characters),$out.characters)"/> 
     <xsl:call-template name="replace.string.section"> 
     <xsl:with-param name="in.string" select="substring-after($in.string,$in.characters)"/> 
     <xsl:with-param name="in.characters" select="$in.characters"/> 
     <xsl:with-param name="out.characters" select="$out.characters"/> 
     </xsl:call-template> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:value-of select="$in.string"/> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

llamarlo de la siguiente manera (en este ejemplo sustituye a la línea roturas en la variable $ some.string con un espacio):

<xsl:call-template name="replace.string.section"> 
     <xsl:with-param name="in.string" select="$some.string"/> 
     <xsl:with-param name="in.characters" select="'&#xA;'"/> 
     <xsl:with-param name="out.characters" select="' '"/> 
    </xsl:call-template> 
3

@ La respuesta de JeniT es genial, solo quiero señalar un truco para administrar el espacio en blanco. No estoy seguro de que sea la mejor manera (o incluso una buena forma), pero a mí me funciona por ahora.

(. "S" para el espacio, "e" para vacío, "n" para salto de línea)

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE xsl:transform [ 
    <!ENTITY s "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> </xsl:text>" > 
    <!ENTITY s2 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> </xsl:text>" > 
    <!ENTITY s4 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> </xsl:text>" > 
    <!ENTITY s6 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>  </xsl:text>" > 
    <!ENTITY e "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'></xsl:text>" > 
    <!ENTITY n "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> 
</xsl:text>" > 
]> 

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"> 
<xsl:output method="text"/> 
<xsl:template match="/"> 
    &e;Flush left, despite the indentation.&n; 
    &e; This line will be output indented two spaces.&n; 

     <!-- the blank lines above/below won't be output --> 

    <xsl:for-each select="//foo"> 
    &e; Starts with two blanks: <xsl:value-of select="@bar"/>.&n; 
    &e; <xsl:value-of select="@baz"/> The 'e' trick won't work here.&n; 
    &s2;<xsl:value-of select="@baz"/> Use s2 instead.&n; 
    &s2; <xsl:value-of select="@abc"/> <xsl:value-of select="@xyz"/>&n; 
    &s2; <xsl:value-of select="@abc"/>&s;<xsl:value-of select="@xyz"/>&n; 
    </xsl:for-each> 
</xsl:template> 
</xsl:transform> 

Aplicado a:

<?xml version="1.0" encoding="UTF-8"?> 
<foo bar="bar" baz="baz" abc="abc" xyz="xyz"></foo> 

Salidas:

Flush left, despite the indentation. 
    This line will be output indented two spaces. 
    Starts with two blanks: bar. 
baz The 'e' trick won't work here. 
    baz Use s2 instead. 
    abcxyz 
    abc xyz 

El truco "e" funciona antes de que un nodo de texto contenga al menos un carácter que no sea de espacio en blanco porque se expande a esto:

<xsl:template match="/"> 
    <xsl:text></xsl:text>Flush left, despite the indentation.<xsl:text> 
</xsl:text> 

Desde el rules for stripping whitespace dicen que los nodos de texto por espacios en blanco sólo se les desnudaron, la nueva línea y sangría entre el < xsl: template > y < xsl: text > obtener despojado (bueno). Dado que las reglas dicen que un nodo de texto con al menos un carácter de espacio en blanco se conserva, el nodo de texto implícito que contiene " This line will be output indented two spaces." mantiene su espacio en blanco inicial (pero supongo que esto también depende de la configuración para eliminar/conservar/normalizar). El "& n;" al final de la línea inserta una nueva línea, pero también asegura que se ignore cualquier espacio en blanco siguiente, ya que aparece entre dos nodos.

El problema que tengo es cuando quiero dar salida a una línea sangrada que comienza con un XSL <: valor de >. En ese caso, el "& e;" no ayudará, porque el espacio en blanco de la indentación no está "conectado" a ningún carácter que no sea de espacios en blanco. Entonces, para esos casos, uso "& s2;" o "& s4;", dependiendo de la cantidad de sangría que desee.

Es un truco feo estoy seguro, pero por lo menos no tienen la verbosa "< xsl: text >" etiquetas ensuciando mi XSLT, y por lo menos todavía puede sangrar la XSLT en sí por lo que es legible. Siento que estoy abusando de XSLT por algo para lo que no fue diseñado (procesamiento de texto) y esto es lo mejor que puedo hacer.


Editar: En respuesta a los comentarios, esto es lo que parece, sin las "macros":

<xsl:template match="/"> 
    <xsl:text>Flush left, despite the indentation.</xsl:text> 
    <xsl:text> This line will be output indented two spaces.</xsl:text> 
    <xsl:for-each select="//foo"> 
    <xsl:text> Starts with two blanks: </xsl:text><xsl:value-of select="@bar"/>.<xsl:text> 
</xsl:text> 
    <xsl:text> </xsl:text><xsl:value-of select="@abc"/><xsl:text> </xsl:text><xsl:value-of select="@xyz"/><xsl:text> 
</xsl:text> 
    </xsl:for-each> 
</xsl:template> 

Creo que eso hace que sea menos clara para ver la sangría de salida prevista, y daña la sangría del propio XSL porque las etiquetas de los extremos </xsl:text> deben aparecer en la columna 1 del archivo XSL (de lo contrario, obtendrá espacios en blanco no deseados en el archivo de salida).

+0

@Dan: Primero,' xsl: text' no es detallado, y siempre puede usar concat en 'xsl: value -of'. Segundo, no está procesando texto, su salida es texto sin formato –

+0

@Dan: Last. Su solución está contra XSLT porque esas entidades (declaradas correctamente) son parte de la sintaxis superficial del documento XML (la hoja de estilo, en este caso). Por lo tanto, el reemplazo lleva tiempo en la fase de análisis antes de llegar al procesador XSLT. Una vez que se realizó el reemplazo y hay ** elementos nuevos ** en la hoja de estilo, las reglas para pelar/preservar espacios en blanco solo se aplican. Desde el punto de vista de un lector, no estará claro cuál sería su resultado de la hoja de estilo. –

+0

@Alejandro: gracias por los comentarios. Supongo que no es detallado si ya está acostumbrado a XML ... mi fondo es más lex/yacc/C++, así que definitivamente estoy sintiéndome de mi elemento aquí. Supongo que usar un editor XML frente a un editor de texto podría ser útil. – Dan

Cuestiones relacionadas