Подтвердить что ты не робот

Преобразование XML в экранированный текст в XSLT

Как преобразовать следующий XML в экранированный текст с помощью XSLT?

Источник:

<?xml version="1.0" encoding="utf-8"?>
<abc>
  <def ghi="jkl">
    mnop
  </def>
</abc>

Вывод:

<TestElement>&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;abc&gt;&lt;def ghi="jkl"&gt;
    mnop
  &lt;/def&gt;&lt;/abc&gt;</TestElement>

В настоящее время я пытаюсь использовать следующий XSLT и, похоже, не работает должным образом:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="utf-8" />
  <xsl:template match="/">
    <xsl:variable name="testVar">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
    </xsl:variable>

    <TestElement>
      <xsl:value-of select="$testVar"/>
    </TestElement>
  </xsl:template>
</xsl:stylesheet>

Вывод инструкции XSLT.NET XslCompiledTransform выводится следующим образом:

<?xml version="1.0" encoding="utf-8"?><TestElement>

    mnop

</TestElement>
4b9b3361

Ответ 1

Ваш код работает так, как он делает, потому что xsl:value-of извлекает string-value из набора node.

Чтобы делать то, что вы хотите, я боюсь, что вам придется явно его кодировать:

    <xsl:template match="/">
        <TestElement>
            <xsl:apply-templates mode="escape"/>
        </TestElement>
    </xsl:template>

    <xsl:template match="*" mode="escape">
        <!-- Begin opening tag -->
        <xsl:text>&lt;</xsl:text>
        <xsl:value-of select="name()"/>

        <!-- Namespaces -->
        <xsl:for-each select="namespace::*">
            <xsl:text> xmlns</xsl:text>
            <xsl:if test="name() != ''">
                <xsl:text>:</xsl:text>
                <xsl:value-of select="name()"/>
            </xsl:if>
            <xsl:text>='</xsl:text>
            <xsl:call-template name="escape-xml">
                <xsl:with-param name="text" select="."/>
            </xsl:call-template>
            <xsl:text>'</xsl:text>
        </xsl:for-each>

        <!-- Attributes -->
        <xsl:for-each select="@*">
            <xsl:text> </xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>='</xsl:text>
            <xsl:call-template name="escape-xml">
                <xsl:with-param name="text" select="."/>
            </xsl:call-template>
            <xsl:text>'</xsl:text>
        </xsl:for-each>

        <!-- End opening tag -->
        <xsl:text>&gt;</xsl:text>

        <!-- Content (child elements, text nodes, and PIs) -->
        <xsl:apply-templates select="node()" mode="escape" />

        <!-- Closing tag -->
        <xsl:text>&lt;/</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>&gt;</xsl:text>
    </xsl:template>

    <xsl:template match="text()" mode="escape">
        <xsl:call-template name="escape-xml">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template match="processing-instruction()" mode="escape">
        <xsl:text>&lt;?</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text> </xsl:text>
        <xsl:call-template name="escape-xml">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
        <xsl:text>?&gt;</xsl:text>
    </xsl:template>

    <xsl:template name="escape-xml">
        <xsl:param name="text"/>
        <xsl:if test="$text != ''">
            <xsl:variable name="head" select="substring($text, 1, 1)"/>
            <xsl:variable name="tail" select="substring($text, 2)"/>
            <xsl:choose>
                <xsl:when test="$head = '&amp;'">&amp;amp;</xsl:when>
                <xsl:when test="$head = '&lt;'">&amp;lt;</xsl:when>
                <xsl:when test="$head = '&gt;'">&amp;gt;</xsl:when>
                <xsl:when test="$head = '&quot;'">&amp;quot;</xsl:when>
                <xsl:when test="$head = &quot;&apos;&quot;">&amp;apos;</xsl:when>
                <xsl:otherwise><xsl:value-of select="$head"/></xsl:otherwise>
            </xsl:choose>
            <xsl:call-template name="escape-xml">
                <xsl:with-param name="text" select="$tail"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

Обратите внимание, что это решение игнорирует узлы комментариев и вставляет ненужные узлы пространства имен (поскольку ось namespace:: будет содержать все узлы, унаследованные от родителя). Однако в отношении пространств имен итоговый цитированный XML будет семантически эквивалентен тому примеру, который вы указали в своем ответе (поскольку эти повторяющиеся повторные изменения ничего не меняют).

Кроме того, это не приведет к объявлению <?xml ... ?>, просто потому, что его нет в модели данных XPath 1.0 (это не инструкция обработки). Если вам действительно нужно это на выходе, вам придется вставить его вручную (и убедитесь, что его кодировка соответствует кодированию сериализации вашего XSLT-процессора).

Ответ 2

вместо того, чтобы ускользать, вы можете добавить текст в раздел CDATA. Текст внутри секции CDATA будет игнорироваться синтаксическим анализатором, подобно тому, как он был экранирован.

ваш пример будет выглядеть следующим образом

<TestElement>
<![CDATA[
<abc>
  <def ghi="jkl">
    mnop
  </def>
</abc>
]]>
</TestElement>

используя следующий фрагмент XSLT:

<xsl:text disable-output-escaping="yes">&lt;![CDATA[</xsl:text>
 <xsl:copy-of select="/"/>
 <xsl:text disable-output-escaping="yes">]]</xsl:text>
 <xsl:text disable-output-escaping="yes">&gt;</xsl:text>

Ответ 3

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

http://lenzconsulting.com/xml-to-string/

Ответ 4

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

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

<xsl:template name="Search-And-Replace">
    <xsl:param name="Input-String"/>
    <xsl:param name="Search-String"/>
    <xsl:param name="Replace-String"/>  
    <xsl:choose>
        <xsl:when test="$Search-String and contains($Input-String, $Search-String)">
            <xsl:value-of select="substring-before($Input-String, $Search-String)"/>
            <xsl:value-of select="$Replace-String"/>        
            <xsl:call-template name="Search-And-Replace">
                <xsl:with-param name="Input-String" select="substring-after($Input-String, $Search-String)"/>
                <xsl:with-param name="Search-String" select="$Search-String"/>
                <xsl:with-param name="Replace-String" select="$Replace-String"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$Input-String"/>
        </xsl:otherwise>
    </xsl:choose>   
</xsl:template> 

Тогда это просто вопрос вызова этого шаблона для char, который вы хотите избежать.

<xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="Hi I am a string &amp; I am awesome"/>
            <xsl:with-param name="Search-String" select="'&amp;'"/>
            <xsl:with-param name="Replace-String" select="'&amp;amp;'"/>
    </xsl:call-template>

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

<xsl:template name="EscapeText">
    <xsl:param name="text" />

    <xsl:variable name="a">
    <xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="$text"/>
            <xsl:with-param name="Search-String" select="'&amp;'"/>
            <xsl:with-param name="Replace-String" select="'&amp;amp;'"/>
        </xsl:call-template>            
    </xsl:variable>

    <xsl:variable name="b">     
        <xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="$a"/>
            <xsl:with-param name="Search-String" select="'&quot;'"/>
            <xsl:with-param name="Replace-String" select="'&amp;quot;'"/>
        </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="c">     
        <xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="$b"/>
            <xsl:with-param name="Search-String">&apos;</xsl:with-param>
            <xsl:with-param name="Replace-String" select="'&amp;apos;'"/>
        </xsl:call-template>
    </xsl:variable>         

    <xsl:variable name="d">     
        <xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="$c"/>
            <xsl:with-param name="Search-String" select="'&gt;'"/>
            <xsl:with-param name="Replace-String" select="'&amp;gt;'"/>
        </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="e">
        <xsl:call-template name="Search-And-Replace">
            <xsl:with-param name="Input-String" select="$d"/>
            <xsl:with-param name="Search-String" select="'&lt;'"/>
            <xsl:with-param name="Replace-String" select="'&amp;lt;'"/>
        </xsl:call-template>
    </xsl:variable>     
    <!--this is the final output-->
    <xsl:value-of select="$e"/>     
</xsl:template> 

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

Ответ 5

Вы можете запретить дополнительные узлы пространства имен, добавив тест в выход пространства имен:


<xsl:variable name="curnode" select="."/>
    <xsl:for-each select="namespace::*"> 
       <xsl:variable name="nsuri" select="."/>
       <xsl:if test="$curnode/descendant-or-self::*[namespace-uri()=$nsuri]">
       ...

Ответ 6

Вам нужно использовать XSLT? Потому что по причинам, объясняемым Павлом Минаевым, было бы гораздо проще использовать другой инструмент. Пример с xmlstartlet:

% xmlstarlet escape
<?xml version="1.0" encoding="utf-8"?>
<abc>
  <def ghi="jkl">
    mnop
  </def>
</abc>
[Control-D]
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;abc&gt;
  &lt;def ghi="jkl"&gt;
    mnop
  &lt;/def&gt;
&lt;/abc&gt;

Ответ 7

Если у вас есть доступ к нему, я бы рекомендовал расширение Saxon serialize. Он делает именно то, что вы хотите. Если вы не хотите этого делать, вам придется вручную вставлять ссылки на сущности при создании документа. Это было бы хрупким, но это работало бы для большинства документов:

<xsl:template match="/">
    <TestElement>
        <xsl:apply-templates/>
    </TestElement>
</xsl:template>
<xsl:template match="*">
    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:apply-templates select="@*"/>
    <xsl:text>&gt;</xsl:text>
    <xsl:apply-templates select="node()"/>
    <xsl:text>&lt;/</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text>&gt;</xsl:text>
</xsl:template>
<xsl:template match="@*">
    <xsl:text>&#32;</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text>="</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>"</xsl:text>
</xsl:template>
<xsl:template match="text()">
    <xsl:value-of select="."/>
</xsl:template>

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

Ответ 8

Почему вы не можете просто запустить

<xsl:template match="/">
  <TestElement>
  <xsl:copy-of select="." />
  </TestElement>
</xsl:template>