36 Stimmen

Konvertierung von XML in reinen Text - wie sollte ich ignorieren/behandeln Whitespace in der XSLT?

Ich versuche, eine XML-Datei in das von Dokuwiki verwendete Markup zu konvertieren, indem ich XSLT verwende. Das funktioniert bis zu einem gewissen Grad, aber die Einrückung in der XSL-Datei wird in die Ergebnisse eingefügt. Im Moment habe ich zwei Möglichkeiten: die XSLT-Sache ganz aufzugeben und einen anderen Weg zu finden, um von XML in das Dokuwiki-Markup zu konvertieren, oder etwa 95 % der Leerzeichen aus der XSL-Datei zu löschen, was sie nahezu unlesbar und zu einem Wartungsalptraum macht.

Gibt es eine Möglichkeit, die Einrückung in der XSL-Datei beizubehalten, ohne den gesamten Leerraum an das endgültige Dokument weiterzugeben?

Hintergrund: Ich migriere ein Autodoc-Tool von statischen HTML-Seiten zu Dokuwiki, damit die vom Serverteam entwickelte API vom Anwendungsteam weiter dokumentiert werden kann, wenn das Anwendungsteam auf schlecht dokumentierten Code stößt. Die Logik besteht darin, dass ein Abschnitt jeder Seite für das Autodoc-Tool reserviert ist und dass Kommentare überall außerhalb dieses Blocks erlaubt sind. Ich verwende XSLT, weil wir bereits eine XSL-Datei haben, um XML in XHTML zu konvertieren, und ich gehe davon aus, dass es schneller sein wird, die XSL-Datei neu zu schreiben, als meine eigene Lösung von Grund auf neu zu entwickeln.

Edit: Ah, richtig, wie dumm von mir, ich habe das Einrückungsattribut vergessen. (Andere Hintergrundinformation: Ich bin neu in XSLT.) Andererseits muss ich mich immer noch mit Zeilenumbrüchen befassen. Dokuwiki verwendet Pipes zur Unterscheidung zwischen Tabellenspalten, was bedeutet, dass alle Daten in einer Tabellenzeile in einer Zeile stehen müssen. Gibt es eine Möglichkeit, die Ausgabe von Zeilenumbrüchen (nur gelegentlich) zu unterdrücken, so dass ich eine ziemlich komplexe Logik für jede Tabellenzelle in einer einigermaßen lesbaren Form erstellen kann?

77voto

JeniT Punkte 3610

Es gibt drei Gründe für unerwünschte Leerzeichen im Ergebnis einer XSLT-Transformation:

  1. Leerraum, der zwischen den Knoten des Quelldokuments entsteht
  2. Leerraum, der aus den Knoten des Quelldokuments stammt
  3. Leerzeichen, die aus dem Stylesheet stammen

Ich werde auf alle drei eingehen, denn es kann schwierig sein, zu erkennen, woher der Leerraum kommt, so dass Sie möglicherweise mehrere Strategien anwenden müssen.

Um den Leerraum zwischen den Knoten in Ihrem Quelldokument zu berücksichtigen, sollten Sie <xsl:strip-space> um alle Leerzeichen zwischen zwei Knoten zu entfernen, und verwenden Sie dann <xsl:preserve-space> um den erheblichen Leerraum zu erhalten, der in gemischten Inhalten auftreten kann. Wenn Ihr Quelldokument zum Beispiel so aussieht:

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

dann sollten Sie die Leerzeichen zwischen den Zeilen <ul> y el <li> und zwischen dem </li> y el </ul> , was nicht von Bedeutung ist, aber die Leerzeichen zwischen den <strong> y <em> Elemente, die est signifikant (sonst würde man "Dies ist ein **wichtiger*** Punkt*" erhalten). Verwenden Sie dazu

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

Le site elements Attribut an <xsl:preserve-space> sollte grundsätzlich alle Elemente in Ihrem Dokument auflisten, die gemischten Inhalt haben.

Übrigens: mit <xsl:strip-space> reduziert auch die Größe des Quellbaums im Speicher und macht Ihr Stylesheet effizienter, so dass es sich auch dann lohnt, wenn Sie keine derartigen Leerzeichenprobleme haben.

Um den Leerraum, der innerhalb von Knoten in Ihrem Quelldokument erscheint, zu behandeln, sollten Sie normalize-space() . Zum Beispiel, wenn Sie haben:

<dt>
  a definition
</dt>

und Sie können sicher sein, dass die <dt> Element keine Elemente enthält, mit denen Sie etwas tun wollen, dann können Sie das tun:

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

Die führenden und nachgestellten Leerzeichen werden aus dem Wert des Parameters <dt> Element und Sie erhalten lediglich die Zeichenfolge "a definition" .

Um Leerraum aus dem Stylesheet zu behandeln, was vielleicht das Problem ist, das Sie haben, ist es, wenn Sie Text in einer Vorlage wie dieser haben:

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

XSLT-Stylesheets werden auf die gleiche Weise geparst wie die Quelldokumente, die sie verarbeiten, so dass das obige XSLT als Baum interpretiert wird, der eine <xsl:template> Element mit einer match Attribut, dessen erstes Kind ein Textknoten ist und dessen zweites Kind ein <xsl:value-of> Element mit einer select Attribut. Der Textknoten enthält führende und nachgestellte Leerzeichen (einschließlich Zeilenumbrüche); da es sich um wörtlichen Text im Stylesheet handelt, wird er buchstäblich in das Ergebnis kopiert, mit allen führenden und nachgestellten Leerzeichen.

Aber algunos Leerzeichen in XSLT-Stylesheets werden automatisch entfernt, nämlich die zwischen den Knoten. Sie erhalten keinen Zeilenumbruch in Ihrem Ergebnis, weil es einen Zeilenumbruch zwischen den <xsl:value-of> und das Ende der <xsl:template> .

Um nur den Text zu erhalten, den Sie im Ergebnis haben wollen, verwenden Sie die <xsl:text> Element wie dieses:

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

Der XSLT-Prozessor ignoriert die Zeilenumbrüche und Einrückungen, die zwischen den Knoten erscheinen, und gibt nur den Text innerhalb der <xsl:text> Element.

0 Stimmen

Das war in der Tat hilfreich, aber Ihre Formulierung "zwischen Knoten" verwirrt mich. Ist es nicht so, dass alle Leerzeichen in Textknoten enthalten sind? Was meinen Sie mit "zwischen den Knoten"? Hätte ich Ihren Namen nicht erkannt, hätte ich angenommen, Sie bräuchten eine Vorlesung über die Struktur von XML-Dokumenten.

0 Stimmen

Guter Artikel, danke! Aber genau genommen verwenden Sie den Begriff "Knoten", wo Sie eigentlich "Element" meinen.

0 Stimmen

@LarsH: Ich bin hier außerhalb meines Bereichs (und ein paar Monate zu spät), aber ich denke, dies beantwortet Ihre Frage: w3.org/TR/xslt#strip "...einige Textknoten werden entfernt. Ein Textknoten wird nie entfernt, es sei denn, er enthält nur Leerzeichen." "Ein Textknoten bleibt erhalten, wenn ... der Textknoten mindestens ein Zeichen enthält, das kein Leerzeichen ist."

4voto

Lindsay Punkte 836

Verwenden Sie indent="no" in Ihrem Output-Tag?

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

Wenn Sie xsl:value-of verwenden, können Sie auch disable-output-escaping="yes" verwenden, um Probleme mit Leerzeichen zu vermeiden.

4 Stimmen

Die meiste Zeit wird mit disable-output-escaping ist der falsche Weg, Dinge zu tun. Sie ist nur für sehr begrenzte Situationen gedacht. Jemandem, der es nicht besser weiß, d-o-e so allgemein zu empfehlen, ist wahrscheinlich eher schädlich als hilfreich. Siehe dpawson.co.uk/xsl/sect2/N2215.html#d3702e223

3voto

Dan Punkte 5705

Die Antwort von @JeniT ist großartig, ich möchte nur auf einen Trick zur Verwaltung von Leerzeichen hinweisen. Ich bin mir nicht sicher, ob es der beste Weg ist (oder sogar ein guter Weg), aber es funktioniert für mich im Moment.

("s" für Leerzeichen, "e" für leer, "n" für Zeilenumbruch.)

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

Angewandt auf:

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

Ausgänge:

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

Der "e"-Trick funktioniert vor einem Textknoten, der mindestens ein Zeichen enthält, das kein Leerzeichen ist, weil er sich zu diesem erweitert:

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

Da die Regeln für das Entfernen von Leerzeichen (Whitespace) besagen, dass Textknoten, die nur aus Leerzeichen bestehen, entfernt werden, werden der Zeilenumbruch und die Einrückung zwischen <xsl:template> und <xsl:text> entfernt (gut). Da die Regeln besagen, dass ein Textknoten mit mindestens einem Leerzeichen erhalten bleibt, enthält der implizite Textknoten " This line will be output indented two spaces." behält die führenden Leerzeichen bei (aber das hängt wohl auch von den Einstellungen für Strip/Erhalten/Normalisieren ab). Das "&n;" am Ende der Zeile fügt einen Zeilenumbruch ein, sorgt aber auch dafür, dass das nachfolgende Leerzeichen ignoriert wird, da es zwischen zwei Knoten steht.

Ich habe Probleme, wenn ich eine eingerückte Zeile ausgeben möchte, die mit einem <xsl:value-of> beginnt. In diesem Fall hilft das "&e;" nicht, weil das Leerzeichen für die Einrückung nicht an andere Zeichen "angehängt" ist. In diesen Fällen verwende ich also "&s2;" oder "&s4;", je nachdem, wie stark ich einrücken möchte.

Es ist sicher ein hässlicher Hack, aber wenigstens habe ich nicht die wortreichen "<xsl:text>"-Tags, die meine XSLT übersäen, und wenigstens kann ich die XSLT selbst noch einrücken, damit sie lesbar ist. Ich habe das Gefühl, dass ich XSLT für etwas missbrauche, wofür es nicht konzipiert wurde (Textverarbeitung), und das ist das Beste, was ich tun kann.


Edit : Als Antwort auf die Kommentare: So sieht es ohne die "Makros" aus:

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

Ich denke, das macht es weniger klar, um die beabsichtigte Ausgabe Einrückung zu sehen, und es vermasselt die Einrückung der XSL selbst, weil die </xsl:text> end-Tags müssen in Spalte 1 der XSL-Datei stehen (sonst entstehen unerwünschte Leerzeichen in der Ausgabedatei).

0 Stimmen

@Dan: Erstens, xsl:text ist es nicht ausführlich, und Sie können immer concat auf xsl:value-of . Zweitens: Sie verarbeiten keinen Text, sondern Ihre Ausgabe ist reiner Text.

0 Stimmen

@Dan: Zuletzt. Ihre Lösung ist gegen XSLT, weil diese Entitäten (richtig deklariert) Teil der Oberflächensyntax des XML-Dokuments (in diesem Fall des Stylesheets) sind. Die Ersetzung nimmt also Zeit in der Parsing-Phase in Anspruch, bevor sie den XSLT-Prozessor erreicht. Sobald die Ersetzung durchgeführt wurde und es sind neue Elemente im Stylesheet werden die Regeln zum Entfernen/Erhalten von Leerzeichen nur auf Textknoten angewendet. Aus der Sicht eines Lesers wird nicht klar sein, was das Ergebnis Ihres Stylesheets sein würde.

0 Stimmen

@Alejandro: Danke für das Feedback. Ich nehme an, es ist nicht wortreich, wenn Sie bereits an XML gewöhnt sind... mein Hintergrund ist mehr lex/yacc/C++, so dass ich definitiv aus meinem Element hier fühlen. Ich nehme an, dass die Verwendung eines XML-Editors im Vergleich zu einem Texteditor helfen könnte.

0voto

Odilon Redo Punkte 561

Was Ihre Bearbeitung der neuen Zeilen betrifft, so können Sie diese Vorlage verwenden, um eine Zeichenfolge innerhalb einer anderen Zeichenfolge rekursiv zu ersetzen, und Sie können sie für Zeilenumbrüche verwenden:

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

Rufen Sie es wie folgt auf (in diesem Beispiel werden Zeilenumbrüche in der Variablen $some.string durch ein Leerzeichen ersetzt):

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X