无懈可击的XMLException

本文关键字:XMLException 无懈可击 | 更新日期: 2023-09-27 18:28:32

Background

我使用以下代码序列化一个非常大的List<string>

public static string SerializeObjectToXML<T>(T item)
{
    XmlSerializer xs = new XmlSerializer(typeof(T));
    using (StringWriter writer = new StringWriter())
    {
        xs.Serialize(writer, item);
        return writer.ToString();
    }
}

并使用以下代码对其进行反序列化:

public static T DeserializeXMLToObject<T>(string xmlText)
{
    if (string.IsNullOrEmpty(xmlText)) return default(T);
    XmlSerializer xs = new XmlSerializer(typeof(T));
    using (MemoryStream memoryStream = new MemoryStream(new UnicodeEncoding().GetBytes(xmlText.Replace((char)0x1A, ' '))))
    using (XmlTextReader xsText = new XmlTextReader(memoryStream))
    {
        xsText.Normalization = true;
        return (T)xs.Deserialize(xsText);
    }
}

但是当我反序列化它时,我得到这个异常:

XMLException:XML 文档中存在错误 (217388, 15(。"[]"(十六进制值0x1A(是无效字符。第 217388 行,位置 15。

at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events(

at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader(

问题

为什么xmlText.Replace((char)0x1A, ' ')线不起作用,这是什么巫术?

一些约束

  • 我的代码是C#,框架4,内置于VS2010 Pro中。
  • 我无法在调试模式下查看 xmlText 的值,因为List<string>太大,并且监视窗口仅显示 Unable to evaluate the expression. Not enough storage is available to complete this operation.错误消息。

无懈可击的XMLException

我想

我已经找到了问题。默认情况下,XmlSerializer将允许您生成无效的 XML。

给定代码:

var input = "'u001a";
var writer = new StringWriter();
var serializer = new XmlSerializer(typeof(string));
serializer.Serialize(writer, input);
Console.WriteLine(writer.ToString());

输出为:

<?xml version="1.0" encoding="utf-16"?>
<string>&#x1A;</string>

这是无效的 XML。根据 XML 规范,所有字符引用都必须是有效的字符。有效字符为:

#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

如您所见,不允许将 U+001A(以及所有其他 C0/C1 控制字符(作为引用,因为它们不是有效字符。

解码器给出的错误消息有点误导,如果它说存在无效的字符引用会更清晰。

您可以执行的操作有多种选择。

1(不要让XmlSerializer首先创建无效的文档

您可以使用 XmlWriter ,默认情况下不允许使用无效字符:

var input = "'u001a";
var writer = new StringWriter();
var serializer = new XmlSerializer(typeof(string));
// added following line:
var xmlWriter = XmlWriter.Create(writer);
// then, write via the xmlWriter rather than writer:
serializer.Serialize(xmlWriter, input);
Console.WriteLine(writer.ToString());

这将在序列化发生时引发异常。必须处理此问题并显示适当的错误。

这可能对您没有用,因为您的数据已经存储了这些无效字符。

或 2(删除对此无效字符的引用

也就是说,不要使用 .Replace((char)0x1a, ' ') ,它目前实际上并没有替换您文档中的任何内容,而是使用 .Replace("&#x1A;", " ") .(这不是不区分大小写的,但它是 .NET 生成的。更强大的解决方案是使用不区分大小写的正则表达式。


顺便说一句,XML 1.1 实际上允许对控制字符的引用,只要它们是引用而不是文档中的纯字符。除了 .NET XmlSerializer 不支持版本 1.1 之外,这将解决您的问题。

如果您有现有数据,其中序列化了包含随后无法反序列化的字符的类,则可以使用以下方法清理数据:

public static string SanitiseSerialisedXml(this string serialized)
{
    if (serialized == null)
    {
        return null;
    }
    const string pattern = @"&#x([0-9A-F]{1,2});";
    var sanitised = Regex.Replace(serialized, pattern, match =>
    {
        var value = match.Groups[1].Value;
        int characterCode;
        if (int.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out characterCode))
        {
            if (characterCode >= char.MinValue && characterCode <= char.MaxValue)
            {
                return XmlConvert.IsXmlChar((char)characterCode) ? match.Value : string.Empty;
            }
        }
        return match.Value;
    });
    return sanitised;
}

更好的解决方案是不允许在序列化时根据 Porges 答案的第 1 点对无效字符进行锯齿。此代码涵盖了 Porges 答案的第 2 点(去除对此无效字符的引用(并删除所有无效字符。上面的代码是为了解决我们将序列化数据存储在数据库字段中的问题,因此需要修复遗留数据并在序列化时解决问题不是一种选择。

当遇到 ASCII 控制字符(SYN、NAK 等(时,这个问题也困扰着我们。如果您使用的是XmlWriterSettings,则有一种简单的方法可以禁用此功能,只需利用XmlWriterSettings.CheckCharacters即可符合 XML 1.0 字符规范。

class Program
{
    static void Main(string[] args)
    {
        MyCustomType c = new MyCustomType();
        c.Description = string.Format("Something like this {0}", (char)22);
        var output = c.ToXMLString();
        Console.WriteLine(output);
    }
}
public class MyCustomType
{
    public string Description { get; set; }
    static readonly XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyCustomType));
    public string ToXMLString()
    {
        var settings = new XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true, CheckCharacters = false };
        StringBuilder sb = new StringBuilder();
        using (var writer = XmlWriter.Create(sb, settings))
        {
            xmlSerializer.Serialize(writer, this);
            return sb.ToString();
        }
    }
}

输出将包含编码字符作为&#x16;,而不是引发错误:

未处理的异常:System.InvalidOperationException:生成 XML 文档时出错。 ---> System.ArgumentException:"▬"(十六进制值0x16(是无效字符。
at System.Xml.XmlEncodedRawTextWriter.InvalidXmlChar(Int32 ch, Char* pDst, Boolean entitize( at System.Xml.XmlEncodedRawTextWriter.WriteElementTextBlock(Char* pSrc, Char* pSrcEnd(
at System.Xml.XmlEncodedRawTextWriter.WriteString(String text(
at System.Xml.XmlEncodedRawTextWriterIndent.WriteString(String text(
at System.Xml.XmlWellFormedWriter.WriteString(String text(
at System.Xml.XmlWriter.WriteElementString(String localName, String ns, String value(
at System.Xml.Serialization.XmlSerializationWriter.WriteElementString(String localName, String ns, String value, XmlQualifiedName xsiType