web-dev-qa-db-ja.com

XSLTでXMLをエスケープされたテキストに変換する

XSLTを使用して次のXMLをエスケープテキストに変換するにはどうすればよいですか?

ソース:

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

.NET XslCompiledTransformによるXSLTステートメントの出力は、次のようになります。

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

    mnop

</TestElement>
26
Frank Liao

xsl:value-ofはノードセットの string-value を取得するため、コードはそのように機能します。

あなたが望むことをするために、私はあなたがそれを明示的にコーディングしなければならないことを恐れています:

    <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プロセッサのシリアル化エンコーディングと一致していることを確認してください)。

37
Pavel Minaev

エスケープする代わりに、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>
17
toca

スタックオーバーフローからのコードスニペットを再利用するときにライセンスのあいまいさを懸念する人は、次の3節のBSDライセンスのコードに興味があるかもしれません。

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

4
jbeard4

Pavel Minaevによって提供された回答を実装しようとしましたが、入力文字列の各文字が個別に再帰され、再帰の深さがすぐになくなるため、これは大きな文字列では非常に危険であることを指摘したいと思います。数行のテキストで実行しようとすると、スタックオーバーフローが発生しました(笑)。

代わりに、個々の文字を調べる必要のないテンプレートを使用します。代わりに、置換する必要のある文字列が見つかるまでテキストを出力します。これは、文字をエスケープするために使用できます。

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

1つの文字列で複数の文字をエスケープするために、変数を使用するラッパーテンプレートを使用しました...

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

これは、入力文字列内の個々の文字ごとに再帰する必要がないため、大きな文字列に対してはるかに安全であることが証明されました。

3
StevenP

名前空間出力にテストを追加することにより、余分な名前空間ノードを防ぐことができます。


<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]">
       ...
1
willdarby

アクセスできる場合は、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
Chris Scott