原因是在 XSLT 的内部表示中,"
与"
. 它们都代表 ascii 代码点 0x34。看起来,当 XslCompiledTransform 产生其输出时,它使用"
了合法的地方。我想它仍然会"
在属性值内输出。
在输出"
中产生的问题对您来说是一个问题吗?"
我刚刚使用任意输入文件在 Visual Studio 中运行了以下 XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/*">
<xml>
<xsl:variable name="chars">"'<>&</xsl:variable>
<node a='{$chars}' b="{$chars}">
<xsl:value-of select="$chars"/>
</node>
</xml>
</xsl:template>
</xsl:stylesheet>
输出是:
<xml>
<node a=""'<>&" b=""'<>&">"'<>&</node>
</xml>
正如您所看到的,即使所有五个字符最初都表示为实体,但撇号的产生是'
随处可见的,引号是"
在文本节点中产生的。此外,a
具有分隔符的属性在输出中'
使用分隔符。"
正如我所说,就 XSLT 而言,引号只是一个引号,一个属性只是一个属性。如何在输出中产生这些取决于 XSLT 处理器。
编辑:此行为的根本原因似乎是 XmlWriter 类的行为。对于那些想要更多自定义转义的人来说,一般建议是扩展XmlTextWriter
类。 这个页面有一个看起来相当有前途的实现:
public class KeepEntityXmlTextWriter : XmlTextWriter
{
private static readonly string[] ENTITY_SUBS = new string[] { "'", """ };
private static readonly char[] REPLACE_CHARS = new char[] { '\'', '"' };
public KeepEntityXmlTextWriter(string filename) : base(filename, null) { ; }
private void WriteStringWithReplace(string text)
{
string[] textSegments = text.Split(KeepEntityXmlTextWriter.REPLACE_CHARS);
if (textSegments.Length > 1)
{
for (int pos = -1, i = 0; i < textSegments.Length; ++i)
{
base.WriteString(textSegments[i]);
pos += textSegments[i].Length + 1;
// Assertion: Replace the following if-else when the number of
// replacement characters and substitute entities has grown
// greater than 2.
Debug.Assert(2 == KeepEntityXmlTextWriter.REPLACE_CHARS.Length);
if (pos != text.Length)
{
if (text[pos] == KeepEntityXmlTextWriter.REPLACE_CHARS[0])
base.WriteRaw(KeepEntityXmlTextWriter.ENTITY_SUBS[0]);
else
base.WriteRaw(KeepEntityXmlTextWriter.ENTITY_SUBS[1]);
}
}
}
else base.WriteString(text);
}
public override void WriteString( string text)
{
this.WriteStringWithReplace(text);
}
}
另一方面,MSDN 文档建议使用XmlWriter.Create()
而不是直接实例化 XmlTextWriters。
在 .NET Framework 2.0 版本中,推荐的做法是使用 XmlWriter.Create 方法和 XmlWriterSettings 类创建 XmlWriter 实例。这使您可以充分利用此版本中引入的所有新功能。有关更多信息,请参阅创建 XML 编写器。
解决这个问题的一种方法是使用与上面相同的逻辑,但将其放在一个包装XmlWriter
. 此页面有一个现成的 XmlWrappingWriter 实现,您可以根据需要对其进行修改。
要将上面的代码与XmlWrappingWriter
.
public class KeepEntityWrapper : XmlWrappingWriter
{
public KeepEntityWrapper(XmlWriter baseWriter)
: base(baseWriter)
{
}
private static readonly string[] ENTITY_SUBS = new string[] { "'", """ };
private static readonly char[] REPLACE_CHARS = new char[] { '\'', '"' };
private void WriteStringWithReplace(string text)
{
string[] textSegments = text.Split(REPLACE_CHARS);
if (textSegments.Length > 1)
{
for (int pos = -1, i = 0; i < textSegments.Length; ++i)
{
base.WriteString(textSegments[i]);
pos += textSegments[i].Length + 1;
// Assertion: Replace the following if-else when the number of
// replacement characters and substitute entities has grown
// greater than 2.
Debug.Assert(2 == REPLACE_CHARS.Length);
if (pos != text.Length)
{
if (text[pos] == REPLACE_CHARS[0])
base.WriteRaw(ENTITY_SUBS[0]);
else
base.WriteRaw(ENTITY_SUBS[1]);
}
}
}
else base.WriteString(text);
}
public override void WriteString(string text)
{
this.WriteStringWithReplace(text);
}
}
请注意,这与 基本相同的代码KeepEntityXmlTextWriter
,但使用 XmlWrappingWriter 作为基类并使用不同的构造函数。
我不知道Guard
代码XmlWrappingWriter
在两个地方使用,但鉴于您将自己使用代码,删除这样的行应该是非常安全的。他们只是确保不会将空值传递给构造函数或(在上述情况下不可访问)BaseWriter
属性:
Guard.ArgumentNotNull(baseWriter, "baseWriter");
要创建 的实例XmlWrappingWriter
,您可以根据需要创建一个 XmlWriter,然后使用:
KeepEntityWrapper wrap = new KeepEntityWrapper(writer);
然后您将使用此wrap
变量作为您传递给 XSL 转换的 XmlWriter。