Преобразование XML в обычный текст – как игнорировать/обрабатывать пробелы в XSLT?

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

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

    Атрибут <em> в elements должен содержать список всех элементов в вашем документе, которые имеют смешанное содержимое.

      

    Кроме того, использование normalize-space() также уменьшает размер исходного дерева в памяти и делает вашу таблицу стилей более эффективной, так что это стоит делать, даже если у вас нет проблем с пропусками такого рода.

    Чтобы устранить пробелы в узлах исходного документа, используйте <dt>. Например, если у вас есть:

    <dt>
      a definition
    </dt>
    

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

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

    Начальный и конечный пробелы будут удалены из значения элемента <xsl:template>, и вы просто получите строку match.

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

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

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

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

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

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

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

    Другие советы

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

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

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

    Ответ @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>
    

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

    Проблема у меня возникает, когда я хочу вывести строку с отступом, начинающуюся с <xsl:value-of>.В этом случае «& e;» Не поможет, потому что пробел в отступлении не «прикреплен» к каким-либо не-белым символам.Итак, для этих случаев я использую "& s2;" или "& s4;", в зависимости от того, сколько в отступе я хочу.

    Я уверен, что это уродливый хак, но, по крайней мере, у меня нет многословных тегов «<xsl:text>», засоряющих мой XSLT, и, по крайней мере, я все еще могу делать отступы в самом XSLT, чтобы он был разборчивым.У меня такое чувство, будто я злоупотребляю XSLT для чего-то, для чего он не предназначен (обработка текста), и это лучшее, что я могу сделать.


    Редактировать:В ответ на комментарии, вот как это выглядит без "макросов":

    <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 (в противном случае в выходном файле появятся нежелательные пробелы).

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

    <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>
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top