将 XML 转换为 XSLT 中的转义文本
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1162352/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Converting XML to escaped text in XSLT
提问by Frank Liao
How can I convert the following XML to an escaped text using XSLT?
如何使用 XSLT 将以下 XML 转换为转义文本?
Source:
来源:
<?xml version="1.0" encoding="utf-8"?>
<abc>
<def ghi="jkl">
mnop
</def>
</abc>
Output:
输出:
<TestElement><?xml version="1.0" encoding="utf-8"?><abc><def ghi="jkl">
mnop
</def></abc></TestElement>
Currently, I'm trying the following XSLT and it doesn't seem to work properly:
目前,我正在尝试以下 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>
Output of XSLT statement by the .NET XslCompiledTransform comes out as the following:
.NET XslCompiledTransform 的 XSLT 语句输出如下:
<?xml version="1.0" encoding="utf-8"?><TestElement>
mnop
</TestElement>
回答by Pavel Minaev
Your code works the way it does because xsl:value-ofretrieves the string-valueof the node set.
您的代码按照它的方式工作,因为xsl:value-of检索节点集的字符串值。
To do what you want, I'm afraid that you'll have to code it explicitly:
为了做你想做的事,恐怕你必须明确地编码:
<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>
Note that this solution ignores comment nodes, and inserts unneccessary namespace nodes (as namespace::axis will include all nodes inherited from parent). Regarding namespaces, however, the resulting quoted XML will be semantically equivalent to the example that you provided in your reply (since those repeated redeclarations don't really change anything).
请注意,此解决方案会忽略注释节点,并插入不必要的命名空间节点(因为namespace::轴将包括从父级继承的所有节点)。但是,关于名称空间,结果引用的 XML 在语义上等同于您在回复中提供的示例(因为这些重复的重新声明并没有真正改变任何东西)。
Also, this won't escape the <?xml ... ?>declaration, simply because it is not present in XPath 1.0 data model (it's not a processing instruction). If you actually need it in the output, you'll have to insert it manually (and make sure that encoding it specifies is consistent with serialization encoding of your XSLT processor).
此外,这不会逃避<?xml ... ?>声明,仅仅因为它不存在于 XPath 1.0 数据模型中(它不是处理指令)。如果在输出中确实需要它,则必须手动插入它(并确保它指定的编码与 XSLT 处理器的序列化编码一致)。
回答by toca
instead of escaping you can add the text inside a CDATA section. Text inside a CDATA section will be ignored by the parser, similar to if it was escaped.
您可以在 CDATA 部分中添加文本而不是转义。CDATA 部分内的文本将被解析器忽略,类似于它被转义。
your example would look like this
你的例子看起来像这样
<TestElement>
<![CDATA[
<abc>
<def ghi="jkl">
mnop
</def>
</abc>
]]>
</TestElement>
using following XSLT snippet:
使用以下 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>
回答by jbeard4
Anyone who is concerned about licensing ambiguity when reusing code snippets from stack overflow may be interested in the following 3-clause BSD-licensed code, which appears to do what is requested by the original poster:
任何在重用堆栈溢出中的代码片段时担心许可歧义的人可能会对以下 3 条款 BSD 许可代码感兴趣,它似乎执行了原始发布者的要求:
回答by StevenP
I attempted to implement the answer provided by Pavel Minaev and want to point out that this is very dangerous for large strings as each character in the input string is recursed over individually, causing the recursion depth to quickly run out. I attempted to run it over a few lines of text and it caused a stack overflow (lol).
我试图实现 Pavel Minaev 提供的答案,并想指出这对于大字符串来说非常危险,因为输入字符串中的每个字符都是单独递归的,导致递归深度很快用完。我试图在几行文本上运行它,它导致堆栈溢出(lol)。
Instead, I use a template that does not need to examine each individual char, rather it will out put the text until it finds a string that needs to be replaced. This can then be used to escape characters:
相反,我使用的模板不需要检查每个单独的字符,而是会输出文本,直到找到需要替换的字符串。然后可以使用它来转义字符:
<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>
Then its just a matter of calling that template for the char that you want to escape..
然后它只是为要转义的字符调用该模板的问题..
<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>
In order to escape multiple characters in the one string, I used a wrapper template that uses variables...
为了转义一个字符串中的多个字符,我使用了一个使用变量的包装模板......
<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>
This proved to be much safer for large strings as it no longer has to recurse for each individual character in the input string.
事实证明,这对于大字符串更安全,因为它不再需要对输入字符串中的每个单独字符进行递归。
回答by willdarby
You can prevent the extra namespace nodes by adding a test in the namespace output:
您可以通过在命名空间输出中添加测试来防止额外的命名空间节点:
<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]">
...
回答by bortzmeyer
Do you needto use XSLT? Because, for reasons explained by Pavel Minaev, it would be much simpler to use another tool. An example with xmlstartlet:
你需要使用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>
回答by Chris Scott
If you have access to it, I would recommend the Saxon extention serialize. It does exactly what you want it to do. If you don't want to do that, you'd have to manually insert the entity references as you build the document. It'd be brittle, but it would work for most documents:
如果您可以访问它,我会推荐 Saxon 扩展序列化。它完全符合您的要求。如果您不想这样做,则必须在构建文档时手动插入实体引用。它会很脆弱,但它适用于大多数文档:
<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>
Most notably, this will probably break if your attributes have the double-quote character. It's really better to use saxon, or to use a user-written extention that uses a proper serializer if you can't.
最值得注意的是,如果您的属性具有双引号字符,这可能会中断。如果不能,最好使用 saxon,或者使用使用适当序列化程序的用户编写的扩展。
回答by Tim Ebenezer
Why can't you just run
你为什么不能跑
<xsl:template match="/">
<TestElement>
<xsl:copy-of select="." />
</TestElement>
</xsl:template>

