XslCompiledTransform.Transform用真实引号替换“”“

时间:2013-01-25 17:46:00

标签: xslt double-quotes xslcompiledtransform

做这样的事情:

using (XmlWriter myMamlHelpWriter = XmlWriter.Create(myFileStream, XmlHelpExToMamlXslTransform.OutputSettings))
{
    XmlHelpExToMamlXslTransform.Transform(myMsHelpExTopicFilePath, null, myMamlHelpWriter);
}

,其中

private static XslCompiledTransform XmlHelpExToMamlXslTransform
{
    get
    {
        if (fMsHelpExToMamlXslTransform == null)
        {
            // Create the XslCompiledTransform and load the stylesheet.
            fMsHelpExToMamlXslTransform = new XslCompiledTransform();
            using (Stream myStream = typeof(XmlHelpBuilder).Assembly.GetManifestResourceStream(
                typeof(XmlHelpBuilder),
                MamlXmlTopicConsts.cMsHelpExToMamlTransformationResourceName))
            {
                XmlTextReader myReader = new XmlTextReader(myStream);
                fMsHelpExToMamlXslTransform.Load(myReader, null, null);
            }
        }

        return fMsHelpExToMamlXslTransform;
    }
}

每次字符串“& quot;”在结果文件中替换为实际引号 无法理解为什么会发生这种情况......

3 个答案:

答案 0 :(得分:1)

原因是在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">&quot;&apos;&lt;&gt;&amp;</xsl:variable>
      <node a='{$chars}' b="{$chars}">
        <xsl:value-of select="$chars"/>
      </node>
    </xml>
  </xsl:template>
</xsl:stylesheet>

输出结果为:

<xml>
  <node a="&quot;'&lt;&gt;&amp;" b="&quot;'&lt;&gt;&amp;">"'&lt;&gt;&amp;</node>
</xml>

正如您所看到的,即使所有五个字符最初都表示为实体,但每个地方都会产生'的引用,并且在文本节点中将引号生成为"。此外,具有a分隔符的'属性在输出中使用"分隔符。正如我所说,就XSLT而言,引号只是一个引号,而一个属性只是一个属性。如何在输出中生成这些内容取决于XSLT处理器。

编辑:此行为的根本原因似乎是XmlWriter类的行为。对于那些想要更多自定义转义的人来说,看起来像是扩展XmlTextWriter类的一般建议。 This page的实施看起来非常有希望:

public class KeepEntityXmlTextWriter : XmlTextWriter
{
    private static readonly string[] ENTITY_SUBS = new string[] { "&apos;", "&quot;" };
    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 documentation建议使用XmlWriter.Create()而不是直接实例化XmlTextWriters。

  
    

在.NET Framework 2.0版本中,建议的做法是使用XmlWriter.Create方法和XmlWriterSettings类创建XmlWriter实例。这使您可以充分利用此版本中引入的所有新功能。有关更多信息,请参阅创建XML Writer。

  

一种方法是使用与上面相同的逻辑,但将其放在包含XmlWriter的类中。 This page有一个现成的XmlWrappingWriter实现,您可以根据需要进行修改。

要将上述代码与XmlWrappingWriter一起使用,您可以将包装编写器子类化,如下所示:

public class KeepEntityWrapper : XmlWrappingWriter
{
    public KeepEntityWrapper(XmlWriter baseWriter)
        : base(baseWriter)
    {
    }

    private static readonly string[] ENTITY_SUBS = new string[] { "&apos;", "&quot;" };
    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,但鉴于您将自己使用代码,删除类似的行应该是非常安全的这个。它们只是确保不将null值传递给构造函数或(在上面的情况下无法访问)BaseWriter属性:

Guard.ArgumentNotNull(baseWriter, "baseWriter");

要创建XmlWrappingWriter的实例,您需要创建一个XmlWriter,然后使用:

KeepEntityWrapper wrap = new KeepEntityWrapper(writer);

然后你将这个wrap变量用作传递给XSL转换的XmlWriter。

答案 1 :(得分:1)

XSLT处理器不知道字符是否由字符实体表示。这是因为XML解析器用任何代码值替换任何字符实体。

因此,XSLT处理器将看到完全相同的字符,无论它是表示为“或&quot;还是&#x22;还是&#34;

使用所谓的 "character maps" ,可以在XSLT 2.0中实现您的目标。

答案 2 :(得分:0)

这是你想要的技巧:

  1. 将所有&替换为&amp;
  2. 执行XSLT
  3. 将所有&amp;替换为&