Conversione da XML a testo semplice: come devo ignorare / gestire gli spazi bianchi in XSLT?

StackOverflow https://stackoverflow.com/questions/184431

  •  06-07-2019
  •  | 
  •  

Domanda

Sto cercando di convertire un file XML nel markup utilizzato da dokuwiki, usando XSLT. In realtà questo funziona in una certa misura, ma il rientro nel file XSL viene inserito nei risultati. Al momento, ho due possibilità: abbandonare del tutto questa cosa XSLT e trovare un altro modo per convertire da XML a markup dokuwiki o eliminare circa il 95% dello spazio bianco dal file XSL, rendendolo quasi illeggibile e un incubo di manutenzione.

Esiste un modo per mantenere il rientro nel file XSL senza passare tutto quello spazio bianco al documento finale?

Background: sto migrando uno strumento di autocodoc da pagine HTML statiche a dokuwiki, quindi l'API sviluppata dal team del server può essere ulteriormente documentata dal team delle applicazioni ogni volta che il team delle app si imbatte in codice scarsamente documentato. La logica è quella di avere una sezione di ogni pagina riservata allo strumento di autodoc e consentire commenti ovunque all'esterno di questo blocco. Sto usando XSLT perché abbiamo già il file XSL da convertire da XML a XHTML, e presumo che sarà più veloce riscrivere l'XSL che lanciare la mia soluzione da zero.

Modifica: Ah, giusto, sciocco, ho trascurato l'attributo indent. (Altra nota di fondo: sono nuovo di XSLT.) D'altra parte, devo ancora occuparmi di nuove linee. Dokuwiki utilizza le pipe per distinguere tra le colonne della tabella, il che significa che tutti i dati in una riga della tabella devono essere su una riga. C'è un modo per sopprimere l'output di nuove righe (solo occasionalmente), quindi posso fare una logica abbastanza complessa per ogni cella della tabella in una fasion un po 'leggibile?

È stato utile?

Soluzione

Esistono tre motivi per ottenere spazi bianchi indesiderati nel risultato di una trasformazione XSLT:

  1. spazio bianco che viene tra i nodi nel documento di origine
  2. spazio bianco proveniente dai nodi nel documento di origine
  3. spazio bianco che viene dal foglio di stile

Parlerò di tutti e tre perché può essere difficile dire da dove viene lo spazio bianco, quindi potrebbe essere necessario utilizzare diverse strategie.

Per indirizzare lo spazio bianco tra i nodi nel documento di origine, è necessario utilizzare <xsl:strip-space> per eliminare qualsiasi spazio bianco che appare tra due nodi, quindi utilizzare <xsl:preserve-space> per preservare lo spazio bianco significativo che potrebbe apparire all'interno di contenuto misto . Ad esempio, se il documento di origine è simile a:

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

quindi vorrai ignorare lo spazio bianco tra <ul> e <li> e tra </li> e </ul>, il che non è significativo, ma preserva lo spazio bianco tra <strong> e <= > elementi, che è significativo (altrimenti otterrai " Questo è un ** punto *** importante * "). Per fare questo usa

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

L'attributo <em> su elements dovrebbe fondamentalmente elencare tutti gli elementi del tuo documento con contenuto misto.

  

A parte: l'uso di normalize-space() riduce anche le dimensioni dell'albero di origine in memoria e rende il foglio di stile più efficiente, quindi vale la pena farlo anche se non si hanno problemi di spazio bianco di questo tipo.

Per indirizzare lo spazio bianco che appare all'interno dei nodi nel documento di origine, è necessario utilizzare <dt>. Ad esempio, se hai:

<dt>
  a definition
</dt>

e puoi essere sicuro che l'elemento "a definition" non conterrà alcun elemento con cui vuoi fare qualcosa, quindi puoi farlo:

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

Lo spazio bianco iniziale e finale verrà rimosso dal valore dell'elemento <xsl:template> e otterrai solo la stringa match.

Per indirizzare gli spazi bianchi provenienti dal foglio di stile, che è forse quello che stai vivendo, è quando hai del testo all'interno di un modello come questo:

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

I fogli di stile XSLT vengono analizzati allo stesso modo dei documenti di origine che elaborano, quindi l'XSLT sopra è interpretata come un albero che contiene un elemento <xsl:value-of> con un attributo select il cui primo figlio è un nodo di testo e il cui il secondo figlio è un elemento <xsl:text> con un attributo <=>. Il nodo di testo ha spazi bianchi iniziali e finali (comprese le interruzioni di riga); dal momento che è un testo letterale nel foglio di stile, viene letteralmente copiato nel risultato, con tutto lo spazio bianco iniziale e finale.

Ma alcuni gli spazi bianchi nei fogli di stile XSLT vengono rimossi automaticamente, cioè quelli tra i nodi. Non si ottiene un'interruzione di riga nel risultato perché esiste un'interruzione di riga tra <=> e la chiusura di <=>.

Per ottenere solo il testo desiderato nel risultato, utilizzare l'elemento <=> in questo modo:

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

Il processore XSLT ignorerà le interruzioni di riga e il rientro che compaiono tra i nodi e genererà solo il testo all'interno dell'elemento <=>.

Altri suggerimenti

Stai usando indent = " no " nel tuo tag di output?

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

Anche se stai usando xsl: value-of puoi usare disable-output-escaping = " yes " per aiutare con alcuni problemi di spazi bianchi.

La risposta di @ JeniT è fantastica, voglio solo sottolineare un trucco per la gestione degli spazi bianchi. Non sono sicuro che sia il modo migliore (o anche un buon modo), ma per ora funziona per me.

(" s " per spazio, " e " per vuoto, " n " per newline.)

<?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>

Applicato a:

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

Uscite:

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

Il trucco 'e' funziona prima di un nodo di testo contenente almeno un carattere non bianco perché si espande fino a questo:

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

Poiché le regole per la rimozione degli spazi bianchi affermano che i nodi di testo solo per gli spazi bianchi vengono rimossi, la nuova riga e il rientro tra < xsl: template > e < xsl: text > essere spogliato (bene). Dato che le regole dicono che un nodo di testo con almeno un carattere di spazio bianco è conservato, il nodo di testo implicito contenente " This line will be output indented two spaces." mantiene il suo spazio di comando principale (ma suppongo che ciò dipenda anche dalle impostazioni per strip / preservare / normalizzare). Il & Quot; & Amp; n; & Quot; alla fine della riga inserisce una nuova riga, ma assicura anche che qualsiasi spazio bianco seguente venga ignorato, poiché appare tra due nodi.

Il problema che ho è quando voglio produrre una riga rientrata che inizia con un < xsl: value-of > ;. In tal caso, il & Quot; & Amp; e; & Quot; non aiuterà, perché lo spazio bianco di rientro non è " allegato " a tutti i caratteri non bianchi. Quindi, per quei casi, uso & Quot; & Amp; s2; & Quot; oppure " & amp; s4; " ;, a seconda della quantità di rientro che voglio.

Sono sicuro che sia un brutto hack, ma almeno non ho il verboso " < xsl: text > " tag che sporcano la mia XSLT, e almeno riesco ancora a rientrare la stessa XSLT in modo che sia leggibile. Mi sembra di abusare di XSLT per qualcosa per cui non è stato progettato (elaborazione del testo) e questo è il meglio che posso fare.


Modifica In risposta ai commenti, ecco come appare senza & Quot; macros & Quot ;:

<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>

Penso che renda meno chiaro il rientro di output previsto e rovini il rientro dell'XSL stesso perché i tag </xsl:text> devono apparire nella colonna 1 del file XSL (altrimenti si ottengono spazi bianchi indesiderati nel file di output).

Per quanto riguarda la modifica relativa a nuove linee, puoi utilizzare questo modello per sostituire ricorsivamente una stringa all'interno di un'altra stringa e puoi usarla per le interruzioni di riga:

<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> 

Chiamalo come segue (questo esempio sostituisce le interruzioni di riga nella variabile $ some.string con uno spazio):

    <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>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top