无懈可击的XMLException

Joh*_*ona 5 c# xml-serialization .net-4.0

背景

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用此代码反序列化它:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

在System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader,String encodingStyle,XmlDeserializationEvents事件)

在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.错误消息.

por*_*ges 8

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

鉴于代码:

var input = "\u001a";

var writer = new StringWriter();
var serializer = new XmlSerializer(typeof(string));
serializer.Serialize(writer, input);

Console.WriteLine(writer.ToString());
Run Code Online (Sandbox Code Playgroud)

输出是:

<?xml version="1.0" encoding="utf-16"?>
<string>&#x1A;</string>
Run Code Online (Sandbox Code Playgroud)

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

#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,U + 001A(和所有其他C0/C1控制字符)没有允许作为参考,因为它们是无效的字符.

解码器给出的错误消息有点误导,如果它说有一个无效的字符引用会更清楚.

您可以做多种选择.

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

您可以使用an 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());
Run Code Online (Sandbox Code Playgroud)

这将在序列化发生时抛出异常.这将必须处理并显示适当的错误.

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

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

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


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


jim*_*974 8

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

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;
}
Run Code Online (Sandbox Code Playgroud)

优选的解决方案是不允许按照Porges回答的第1点在序列化点上对无效字符进行连接.此代码涵盖了Porges答案的第2点(删除了对此无效字符的引用)并删除了所有无效字符.编写上面的代码是为了解决我们在数据库字段中存储序列化数据的问题,因此需要修复遗留数据并在序列化时解决问题不是一种选择.