【问题标题】:Prevent XmlDocument from interpreting HTML Code防止 XmlDocument 解释 HTML 代码
【发布时间】:2023-04-09 06:44:02
【问题描述】:

我构建了一个代理来动态修改 XML 格式的数据。

问题是某些 XML 文档在属性中包含 HTML 十进制代码,并被 XmlDocument.LoadXml 方法解释。

输入文档示例:

<config>
   <var name="CultureCode" value="fr-FR" />
   <var name="CurrencyFormatting">
      <var name="Symbol"               value="&#8364;" />
      <var name="Code"                 value="EUR"/>
      <var name="ThousandSeparator"    value=" " />
      <var name="DecimalSeparator"    value="," />
   </var>  
</config>

我用下面的方法来修改这段XML:

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString); //Decodes HTML Decimal Code
            XmlNode node = xmlDoc.SelectSingleNode(".//var[@name='DecimalSeparator']");
            node.Attributes["value"].Value = ".";
            string modified = xmlDoc.OuterXml;

但是,问题在于生成的 XML 不再包含 &amp;amp;#8364;,而是包含 符号,这被下游进程误解了。

<config>
   <var name="CultureCode" value="fr-FR" />
   <var name="CurrencyFormatting">
      <var name="Symbol"               value="€" /> <!-- Value has changed -->
      <var name="Code"                 value="EUR"/>
      <var name="ThousandSeparator"    value=" " />
      <var name="DecimalSeparator"    value="." />
   </var>  
</config>

如何防止这种行为?是否有任何其他包在默认情况下不会解释 HTML 代码?

我可以从结果字符串中替换 '€',但我永远无法确定是否已经解释了另一段 HTML 十进制代码,这并不理想。

【问题讨论】:

  • 问题是XmlDocument做的事情是合法且必要的:属性里面有很多非法字符,所以必须转义,所以里面有转义字符是很正常的一个属性,XmlDocument 取消转义是很正常的。
  • 您是否尝试将&amp;amp;#8364; 更改为&amp;amp;#8364;?在您输入的 XML 中?
  • @xanatos 理解。那么,作为一种解决方法,我怎样才能扭转这个过程并转义所有属性?
  • 现在...我做了一些研究,我有 75% 的把握 XmlReaderXmlTextReader(几乎所有 .NET 类使用的读者)取消转义 xml 实体。你无法控制它。您可以在 XmlWriter 方面工作,但这仍然很痛苦。

标签: c# xml-parsing


【解决方案1】:

正如我所说,这是一种痛苦。 XmlReaderXmlTextReader(这是 XmlDocument 和几乎所有其他“高级” .NET xml 类使用的阅读器)会自动取消转义它们遇到的所有 xml char 实体,并且无法禁用此功能。因此,除非重写一大块 XmlReader,否则不可能保留原始的 xml 字符实体。可以做的是在XmlWriter 方面工作。还是很痛。

public class EscapedXmlWriter : XmlWriter
{
    // Note that normally XmlWriter uses hex escapes
    public bool UseHexCharEntities { get; set; } = false;

    protected XmlWriter Writer;

    public EscapedXmlWriter(XmlWriter writer)
    {
        Writer = writer;
    }

    public override System.Xml.WriteState WriteState => Writer.WriteState;

    public override void Flush() => Writer.Flush();

    public override string LookupPrefix(string ns) => Writer.LookupPrefix(ns);

    public override void WriteBase64(byte[] buffer, int index, int count) => Writer.WriteBase64(buffer, index, count);

    public override void WriteCData(string text) => Writer.WriteCData(text);

    public override void WriteCharEntity(char ch) => Writer.WriteCharEntity(ch);

    public override void WriteChars(char[] buffer, int index, int count) => Writer.WriteChars(buffer, index, count);

    // We could give to comments the same treatment of attributes and content 
    public override void WriteComment(string text) => Writer.WriteComment(text);

    public override void WriteDocType(string name, string pubid, string sysid, string subset) => Writer.WriteDocType(name, pubid, sysid, subset);

    public override void WriteEndAttribute() => Writer.WriteEndAttribute();

    public override void WriteEndDocument() => Writer.WriteEndDocument();

    public override void WriteEndElement() => Writer.WriteEndElement();

    public override void WriteEntityRef(string name) => Writer.WriteEntityRef(name);

    public override void WriteFullEndElement() => Writer.WriteFullEndElement();

    public override void WriteProcessingInstruction(string name, string text) => Writer.WriteProcessingInstruction(name, text);

    public override void WriteRaw(char[] buffer, int index, int count) => Writer.WriteRaw(buffer, index, count);

    public override void WriteRaw(string data) => Writer.WriteRaw(data);

    public override void WriteStartAttribute(string prefix, string localName, string ns) => Writer.WriteStartAttribute(prefix, localName, ns);

    public override void WriteStartDocument() => Writer.WriteStartDocument();

    public override void WriteStartDocument(bool standalone) => Writer.WriteStartDocument(standalone);

    public override void WriteStartElement(string prefix, string localName, string ns) => Writer.WriteStartElement(prefix, localName, ns);

    public override void WriteString(string text)
    {
        Trace.WriteLine($"{Writer.WriteState}: {text}");

        var isAttribute = Writer.WriteState == System.Xml.WriteState.Attribute;

        var sb = new StringBuilder();

        for (int i = 0; i < text.Length; i++)
        {
            char ch = text[i];
            char low;

            if (ch == '"' && isAttribute)
            {
                sb.Append("&quot;");
            }
            if (ch == '\'' && isAttribute)
            {
                // XmlWriter doesn't escape ' in attributes because it always uses the form "xyz" instead of the form 'xyz'
                sb.Append("&apos;");
            }
            else if (ch == '&')
            {
                sb.Append("&amp;");
            }
            else if (ch == '<')
            {
                sb.Append("&lt;");
            }
            else if (ch == '>')
            {
                sb.Append("&gt;");
            }
            else if (i + 1 < text.Length && char.IsHighSurrogate(ch) && char.IsLowSurrogate(low = text[i + 1]))
            {
                int utf32 = char.ConvertToUtf32(ch, low);

                if (UseHexCharEntities)
                {
                    sb.Append("&#x");
                    sb.Append(utf32.ToString("X"));
                }
                else
                {
                    sb.Append("&#");
                    sb.Append(utf32);
                }

                sb.Append(';');

                i++;
            }
            else if (ch < 0x20 || ch > 0x7f)
            {
                if ((ch == '\r' || ch == '\n' || ch == '\t') && !isAttribute)
                {
                    sb.Append(ch);
                }
                else
                {
                    if (UseHexCharEntities)
                    {
                        sb.Append("&#x");
                        sb.Append(((int)ch).ToString("X"));
                    }
                    else
                    {
                        sb.Append("&#");
                        sb.Append((int)ch);
                    }

                    sb.Append(';');
                }
            }
            else
            {
                sb.Append(ch);
            }
        }

        string text2 = sb.ToString();
        Writer.WriteRaw(text2);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar) => Writer.WriteSurrogateCharEntity(lowChar, highChar);

    public override void WriteWhitespace(string ws) => Writer.WriteWhitespace(ws);

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            Writer.Dispose();
        }

        base.Dispose(disposing);
    }
}

这是一个EscapedXmlWriter,它将转义所有字符&lt; 0x20&gt; 0x7f(因此它将转义任何控制字符或非ASCII 字符)。它将在内容中保留新的行/换行符/标签,否则内容将变得难以辨认。甚至还有对非 BMP 字符的特殊处理(例如?)。并且有一个选项UseHexCharEntities 来控制是否应该使用十六进制或十进制转义。

像这样使用它:

using (var writer = new EscapedXmlWriter(XmlWriter.Create("out.xml", new XmlWriterSettings
{
    Indent = true,
})))
{
    var doc = new XmlDocument();
    doc.Load("XMLFile1.xml");

    // THIS!
    doc.WriteTo(writer);
}

XmlWriter.Create() 非常灵活。您可以将其“连接”到 StringBuilderStreamStringWriter

var sb = new StringBuilder();
XmlWriter.Create(sb, new XmlWriterSettings { ... })

【讨论】:

  • 它就像一个魅力,非常感谢:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多