Konwersja XML na zwykły tekst - jak powinienem ignorować / obsługiwać białe znaki w XSLT?

Próbuję przekonwertować plik XML na znaczniki używane przez DokuWiki, używając XSLT. To faktycznie działa do pewnego stopnia, ale Wcięcie w pliku XSL jest wstawiane do wyników. W tej chwili mam dwie opcje: porzucić tę sprawę XSLT całkowicie i znaleźć inny sposób na konwersję z XML do DokuWiki markup, lub usunąć około 95% białych znaków z pliku XSL, czyniąc go prawie nieczytelnym i koszmarem konserwacji.

Czy Jest jakiś sposób na utrzymanie wcięcia w plik XSL bez przekazywania całej białej spacji do dokumentu końcowego?

Tło: migruję narzędzie autodoc ze statycznych stron HTML do dokuwiki, więc API opracowane przez zespół serwerów może być dalej udokumentowane przez zespół aplikacji, gdy Zespół aplikacji natrafi na słabo udokumentowany kod. Logika polega na tym, aby mieć sekcję każdej strony przeznaczoną dla narzędzia autodoc i zezwalać na komentarze w dowolnym miejscu poza tym blokiem. Używam XSLT bo mamy już plik XSL przekonwertować z XML na XHTML, a zakładam, że szybciej będzie przepisać XSL niż wywalić własne rozwiązanie od podstaw.

Edit: no tak, głupi ja, zaniedbałem atrybut wcięcia. (Inna Uwaga tła: jestem nowy w XSLT.) Z drugiej strony, nadal mam do czynienia z newlines. DokuWiki używa rur do rozróżniania kolumn tabeli, co oznacza, że wszystkie dane w linii tabeli muszą znajdować się w jednej linii. Czy jest sposób na powstrzymanie pojawiania się nowych linii (tylko czasami), więc mogę zrobić dość skomplikowaną logikę dla każdej komórki tabeli w nieco czytelnej fasji?

Author: Mathias Müller, 2008-10-08

4 answers

Istnieją trzy powody, dla których w wyniku transformacji XSLT otrzymuje się niechciane spacje:

  1. Biała spacja, która pochodzi z pomiędzy węzłami w dokumencie źródłowym
  2. Biała spacja, która pochodzi z węzłów w dokumencie źródłowym
  3. spacja pochodząca z arkusza stylów

Zamierzam porozmawiać o wszystkich trzech, ponieważ może być trudno powiedzieć, skąd pochodzi Biała spacja, więc może być konieczne użycie kilku strategii.

Na adres Biała spacja pomiędzy węzłami w dokumencie źródłowym powinna być używana <xsl:strip-space>, aby usunąć wszelkie białe spacje, które pojawiają się między dwoma węzłami, a następnie użyć <xsl:preserve-space>, aby zachować znaczące białe spacje, które mogą pojawić się w mieszanej zawartości. Na przykład, jeśli twój dokument źródłowy wygląda następująco:

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

Wtedy będziesz chciał zignorować białą spację pomiędzy <ul> i <li> oraz pomiędzy </li> i </ul>, co nie jest istotne, ale zachować białą spację między <strong> i <em> elementy, które jest znaczące (inaczej otrzymałbyś " to jest **ważny***punkt*"). Aby to zrobić użyj

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

Atrybut elements na <xsl:preserve-space> powinien w zasadzie zawierać listę wszystkich elementów dokumentu, które mają mieszaną zawartość.

Na bok: użycie <xsl:strip-space> zmniejsza również rozmiar drzewa źródłowego w pamięci i sprawia, że arkusz stylów jest bardziej wydajny, więc warto to zrobić, nawet jeśli nie masz problemów z białymi spacjami tego typu.

To zwróć białe znaki, które pojawiają się w węzłach w dokumencie źródłowym, powinieneś użyć normalize-space(). Na przykład, jeśli masz:

<dt>
  a definition
</dt>

I możesz być pewien, że element <dt> nie będzie zawierał żadnych elementów, z którymi chcesz coś zrobić, wtedy możesz to zrobić:

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

Początkowe i końcowe spacje zostaną usunięte z wartości elementu <dt> i po prostu otrzymasz łańcuch "a definition".

Aby adresować białe spacje pochodzące z arkusza stylów, który być może jest jeden, którego doświadczasz, to gdy masz tekst w szablonie takim jak ten:

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

Arkusze stylów XSLT są przetwarzane w taki sam sposób, jak dokumenty źródłowe, które przetwarzają, więc powyższy XSLT jest interpretowany jako drzewo, które zawiera <xsl:template> element z atrybutem match, którego pierwszym potomkiem jest węzeł tekstowy, a drugim potomkiem jest <xsl:value-of> element z atrybutem select. Węzeł tekstowy ma początkowe i końcowe spacje( w tym podziały wierszy); ponieważ jest to dosłowny tekst w arkuszu stylów, jest dosłownie kopiowany do wyniku, ze wszystkimi początkowymi i końcowymi białymi spacjami.

Ale niektóre białe znaki w arkuszach stylów XSLT są usuwane automatycznie, a mianowicie te pomiędzy węzłami. Nie uzyskujesz podziału linii w wyniku, ponieważ istnieje podział linii między <xsl:value-of> a zamknięciem <xsl:template>.

Aby uzyskać tylko tekst, który chcesz w wyniku, użyj <xsl:text> element w ten sposób:

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

Procesor XSLT zignoruje podziały linii i wcięcia, które pojawiają się między węzłami i wypisują tekst tylko wewnątrz elementu <xsl:text>.

 76
Author: JeniT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 21:46:07

Czy używasz indent = " no " w znaczniku wyjściowym?

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

Również jeśli używasz xsl: value-of możesz użyć disable-output-escaping= "yes", aby pomóc w niektórych problemach z białymi spacjami.

 4
Author: Lindsay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 19:26:46

@JeniT odpowiedź jest świetna, chcę tylko wskazać sztuczkę do zarządzania białymi spacjami. Nie jestem pewien, czy jest to najlepszy sposób (lub nawet dobry sposób), ale na razie działa dla mnie.

("s" dla spacji, " e "dla pustej, "n"dla nowej linii.)

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

Zastosowane do:

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

Wyjścia:

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

Sztuczka 'e' działa przed węzłem tekstowym zawierającym co najmniej jeden nie-znak spacji, ponieważ rozszerza się do tego:

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

Od Zasady rozbierania whitespace mówi, że węzły tekstowe zawierające tylko whitespace są usuwane, nowy wiersz i wcięcia pomiędzy

Problem polega na tym, że chcę wypisać linię z wciętą, która zaczyna się od . W takim przypadku "& e; "nie pomoże, ponieważ białe znaki wcięć nie są" dołączane " do znaków innych niż białe. W takich przypadkach używam " & s2; "lub" &s4;", w zależności od tego, ile wcięcia chcę.

To na pewno brzydki hack, ale przynajmniej nie mam napisanych znaczników" " zaśmiecających mój XSLT, a przynajmniej mogę wciśnij sam XSLT, aby był czytelny. Czuję, że nadużywam XSLT za coś, do czego nie został zaprojektowany (przetwarzanie tekstu) i to jest najlepsze, co mogę zrobić.


Edit: W odpowiedzi na komentarze tak to wygląda bez "makr":

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

Myślę, że to sprawia, że jest mniej jasne, aby zobaczyć zamierzone wcięcie wyjściowe, a to spieprza wcięcia samego XSL, ponieważ </xsl:text> znaczniki końca muszą pojawić się w kolumnie 1 pliku XSL (w przeciwnym razie w pliku wyjściowym pojawią się niepożądane spacje).

 3
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-17 18:47:09

Jeśli chodzi o edytowanie nowych wierszy, możesz użyć tego szablonu do rekurencyjnego zastąpienia jednego ciągu w innym łańcuchu i możesz go użyć do łamania wierszy:

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

Wywołaj to w następujący sposób (ten przykład zastępuje podziały wierszy w $some.string zmienna ze spacją):

    <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>
 0
Author: Odilon Redo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 21:07:49