Преобразование XML в экранированный текст в XSLT - PullRequest
25 голосов
/ 22 июля 2009

Как я могу преобразовать следующий 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>

Ответы [ 8 ]

36 голосов
/ 22 июля 2009

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

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

    <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-процессора).

17 голосов
/ 30 октября 2009

вместо экранирования вы можете добавить текст в раздел 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>
4 голосов
/ 25 июля 2010

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

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

3 голосов
/ 28 июня 2013

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

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

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

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

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

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

1 голос
/ 02 декабря 2010

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


<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]">
       ...
0 голосов
/ 23 июля 2009

Если у вас есть доступ к нему, я бы порекомендовал расширение 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>

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

0 голосов
/ 22 июля 2009

Вам нужно для использования 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;
0 голосов
/ 22 июля 2009

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

<xsl:template match="/">
  <TestElement>
  <xsl:copy-of select="." />
  </TestElement>
</xsl:template>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...