Преобразование XML в обычный текст – как игнорировать/обрабатывать пробелы в XSLT?
-
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="'
'"/>
<xsl:with-param name="out.characters" select="' '"/>
</xsl:call-template>