如何使用XSLT将以下XML转换为转义文本?
来源:
<?xml version="1.0" encoding="utf-8"?>
<abc>
<def ghi="jkl">
mnop
</def>
</abc>
输出:
<TestElement><?xml version="1.0" encoding="utf-8"?><abc><def ghi="jkl">
mnop
</def></abc></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>
答案 0 :(得分:36)
您的代码按照它的方式工作,因为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><</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>></xsl:text>
<!-- Content (child elements, text nodes, and PIs) -->
<xsl:apply-templates select="node()" mode="escape" />
<!-- Closing tag -->
<xsl:text></</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>></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><?</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: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;</xsl:when>
<xsl:when test="$head = '<'">&lt;</xsl:when>
<xsl:when test="$head = '>'">&gt;</xsl:when>
<xsl:when test="$head = '"'">&quot;</xsl:when>
<xsl:when test="$head = "'"">&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处理器的序列化编码一致)。
答案 1 :(得分:17)
而不是转义,您可以在CDATA部分中添加文本。 解析器将忽略CDATA部分内的文本,类似于它被转义。
你的例子看起来像这样
<TestElement>
<![CDATA[
<abc>
<def ghi="jkl">
mnop
</def>
</abc>
]]>
</TestElement>
使用以下XSLT代码段:
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:copy-of select="/"/>
<xsl:text disable-output-escaping="yes">]]</xsl:text>
<xsl:text disable-output-escaping="yes">></xsl:text>
答案 2 :(得分:4)
答案 3 :(得分:3)
我试图实现Pavel Minaev提供的答案,并想指出这对于大字符串非常危险,因为输入字符串中的每个字符都会单独递归,导致递归深度快速耗尽。我尝试在几行文本中运行它,导致堆栈溢出(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 & I am awesome"/>
<xsl:with-param name="Search-String" select="'&'"/>
<xsl:with-param name="Replace-String" select="'&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="'&'"/>
<xsl:with-param name="Replace-String" select="'&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="'"'"/>
<xsl:with-param name="Replace-String" select="'&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">'</xsl:with-param>
<xsl:with-param name="Replace-String" select="'&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="'>'"/>
<xsl:with-param name="Replace-String" select="'&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="'<'"/>
<xsl:with-param name="Replace-String" select="'&lt;'"/>
</xsl:call-template>
</xsl:variable>
<!--this is the final output-->
<xsl:value-of select="$e"/>
</xsl:template>
对于大字符串来说,这被证明更安全,因为它不再需要为输入字符串中的每个字符递归。
答案 4 :(得分:1)
您可以通过在命名空间输出中添加测试来阻止额外的命名空间节点:
<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]">
...
答案 5 :(得分:0)
您需要使用XSLT吗?因为Pavel Minaev解释的原因,使用其他工具要简单得多。 xmlstartlet的示例:
% xmlstarlet escape <?xml version="1.0" encoding="utf-8"?> <abc> <def ghi="jkl"> mnop </def> </abc> [Control-D] <?xml version="1.0" encoding="utf-8"?> <abc> <def ghi="jkl"> mnop </def> </abc>
答案 6 :(得分:0)
如果您有权访问它,我会推荐Saxon extention serialize。它完全符合您的要求。如果您不想这样做,则必须在构建文档时手动插入实体引用。它很脆弱,但它适用于大多数文件:
<xsl:template match="/">
<TestElement>
<xsl:apply-templates/>
</TestElement>
</xsl:template>
<xsl:template match="*">
<xsl:text><</xsl:text>
<xsl:value-of select="name()"/>
<xsl:apply-templates select="@*"/>
<xsl:text>></xsl:text>
<xsl:apply-templates select="node()"/>
<xsl:text></</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>></xsl:text>
</xsl:template>
<xsl:template match="@*">
<xsl:text> </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真的更好,或者使用用户编写的扩展,如果你不能使用正确的序列化器。
答案 7 :(得分:-1)
为什么你不能运行
<xsl:template match="/">
<TestElement>
<xsl:copy-of select="." />
</TestElement>
</xsl:template>