Преобразование XML в простой текст - как мне игнорировать / обрабатывать пробелы в XSLT? - PullRequest
36 голосов
/ 08 октября 2008

Я пытаюсь преобразовать файл XML в разметку, используемую dokuwiki, используя XSLT. Это на самом деле работает в некоторой степени, но отступ в файле XSL вставляется в результаты. На данный момент у меня есть два варианта: полностью отказаться от этой вещи XSLT и найти другой способ конвертировать XML в разметку dokuwiki или удалить около 95% пробела из файла XSL, что делает его почти нечитаемым и кошмарным с точки зрения обслуживания.

Есть ли способ сохранить отступ в файле XSL, не пропуская все эти пробелы в окончательном документе?

Справочная информация. Я перевожу инструмент автоматического документирования со статических HTML-страниц на докувики, поэтому разработанные группой серверов API-интерфейсы могут в дальнейшем документироваться группой приложений, когда группа приложений сталкивается с плохо документированным кодом. Логика состоит в том, чтобы выделить раздел каждой страницы для инструмента autodoc и разрешить комментарии в любом месте за пределами этого блока. Я использую XSLT, потому что у нас уже есть файл XSL для преобразования из XML в XHTML, и я предполагаю, что будет быстрее переписать XSL, чем развернуть собственное решение с нуля.

Редактировать: Ах да, глупо, я пренебрег атрибутом отступа. (Другая справочная информация: я новичок в XSLT.) С другой стороны, мне все еще приходится иметь дело с символами новой строки. Докувики использует каналы, чтобы различать столбцы таблицы, что означает, что все данные в строке таблицы должны быть в одной строке. Есть ли способ подавления вывода новых строк (только изредка), поэтому я могу сделать довольно сложную логику для каждой ячейки таблицы в несколько читабельном виде?

Ответы [ 4 ]

77 голосов
/ 09 октября 2008

Существует три причины получения нежелательных пробелов в результате преобразования XSLT:

  1. пробелы, которые появляются между узлами в исходном документе
  2. пробелы, которые находятся внутри узлов исходного документа
  3. пробел, полученный из таблицы стилей

Я собираюсь поговорить обо всех трех, потому что может быть трудно определить, откуда появляются пробелы, поэтому вам, возможно, придется использовать несколько стратегий.

Чтобы устранить пробелы, которые находятся между узлами в вашем исходном документе, вы должны использовать <xsl:strip-space> для удаления любых пробелов, которые появляются между двумя узлами, а затем использовать <xsl:preserve-space> для сохранения значительных пробелов, которые могут появиться в смешанном контенте. , Например, если ваш исходный документ выглядит так:

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

тогда вы захотите проигнорировать пробел между <ul> и <li> и между </li> и </ul>, что не имеет значения, но сохранить пробел между <strong> и <em> элементы, которые значимы (в противном случае вы получите "Это ** важный *** пункт *"). Для этого используйте

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

Атрибут elements в <xsl:preserve-space> должен в основном перечислять все элементы в вашем документе, которые имеют смешанное содержимое.

В сторону: использование <xsl:strip-space> также уменьшает размер исходного дерева в памяти и делает вашу таблицу стилей более эффективной, так что это стоит делать, даже если у вас нет таких проблем с пробелами.

Чтобы устранить пробелы, которые появляются внутри узлов в вашем исходном документе, вы должны использовать normalize-space(). Например, если у вас есть:

<dt>
  a definition
</dt>

и вы можете быть уверены, что элемент <dt> не будет содержать элементы, с которыми вы хотите что-то сделать, тогда вы можете сделать:

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

Начальный и конечный пробелы будут удалены из значения элемента <dt>, и вы просто получите строку "a definition".

Для устранения пробелов из таблицы стилей, которая, возможно, является той, которую вы испытываете, - это когда у вас есть текст в шаблоне, подобный этому:

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

Таблицы стилей XSLT анализируются так же, как и исходные документы, которые они обрабатывают, поэтому приведенный выше XSLT интерпретируется как дерево, содержащее элемент <xsl:template> с атрибутом match, первый дочерний элемент которого является текстовым узлом и чей Второй дочерний элемент - это элемент <xsl:value-of> с атрибутом select. Текстовый узел имеет начальные и конечные пробелы (включая разрывы строк); поскольку это буквальный текст в таблице стилей, он буквально копируется в результат со всеми начальными и конечными пробелами.

Но некоторые пробелы в таблицах стилей XSLT автоматически удаляются, а именно между узлами. Вы не получаете разрыв строки в своем результате, потому что есть разрыв строки между <xsl:value-of> и закрытием <xsl:template>.

Чтобы получить только нужный текст в результате, используйте элемент <xsl:text>, например:

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

Процессор XSLT будет игнорировать разрывы строк и отступы, которые появляются между узлами, и выводит только текст внутри элемента <xsl:text>.

4 голосов
/ 08 октября 2008

Используете ли вы отступ = "нет" в выходном теге?

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

Также, если вы используете xsl: value-of, вы можете использовать disable-output-escaping = "yes" для решения некоторых проблем с пробелами.

3 голосов
/ 16 января 2011

@ Ответ JeniT великолепен, я просто хочу указать на хитрость в управлении пробелами. Я не уверен, что это лучший (или даже хороший) способ, но сейчас он работает для меня.

("s" для пробела, "e" для пустого, "n" для новой строки.)

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

Применительно к:

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

Выходы:

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

Трюк 'e' работает до текстового узла, содержащего хотя бы один непробельный символ, поскольку он расширяется до следующего:

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

Поскольку правила для удаления пробелов говорят, что текстовые узлы только для пробелов удаляются, перевод строки и отступ между и удаляются (хорошо). Поскольку в правилах говорится, что текстовый узел с хотя бы одним символом пробела сохраняется, неявный текстовый узел, содержащий " This line will be output indented two spaces.", сохраняет свой начальный пробел (но я думаю, это также зависит от настроек strip / preserve / normalize). Затем;" в конце строки вставляется символ новой строки, но он также гарантирует, что любой следующий пробел будет проигнорирован, поскольку он появляется между двумя узлами.

Проблема в том, что я хочу вывести строку с отступом, которая начинается с . В этом случае "& e;" не поможет, потому что пробел с отступом не «привязан» ни к каким непробельным символам. Поэтому для этих случаев я использую "& s2;" или "& s4;", в зависимости от того, сколько отступов я хочу.

Я уверен, что это уродливый хак, но, по крайней мере, у меня нет подробных тегов , засоряющих мой XSLT, и, по крайней мере, я все еще могу сделать отступ для самого XSLT, чтобы он был разборчивым. Мне кажется, что я злоупотребляю XSLT для чего-то, для чего он не предназначен (обработка текста), и это лучшее, что я могу сделать.


Edit: В ответ на комментарии вот как это выглядит без «макросов»:

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

Я думаю, что это делает менее ясным просмотр предполагаемого выходного отступа, и он облажает отступ самого XSL, потому что конечные теги </xsl:text> должны появляться в столбце 1 файла XSL (в противном случае вы получите нежелательные пробелы). в выходной файл).

0 голосов
/ 09 октября 2008

Что касается вашего редактирования новых строк, вы можете использовать этот шаблон для рекурсивной замены одной строки в другой строке, и вы можете использовать его для разрывов строк:

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

Вызовите его следующим образом (этот пример заменяет разрывы строк в переменной $ some.string пробелом):

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