无懈可击的XMLException

时间:2012-03-21 02:57:55

标签: c# .net-4.0 xml-serialization

背景

我使用以下代码序列化了一个非常大的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位。

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

3 个答案:

答案 0 :(得分:8)

我想我发现了问题。默认情况下,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版本之外,这将解决您的问题。

答案 1 :(得分: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;
}

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

答案 2 :(得分:2)

当遇到ASCII控制字符( SYN, NAK, etc )时,这个问题也困扰着我们。如果您使用的是XmlWriterSettings,只需使用XmlWriterSettings.CheckCharacters conformance with XML 1.0 Characters specifications即可停用此功能。

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文档时出错。 ---&GT; System.ArgumentException:&#39;▬&#39;,十六进制值0x16,是无效字符。   
在System.Xml.XmlEncodedRawTextWriter.InvalidXmlChar(Int32 ch,Char * pDst,Boolean entitize)      在System.Xml.XmlEncodedRawTextWriter.WriteElementTextBlock(Char * pSrc,Char * pSrcEnd)   
在System.Xml.XmlEncodedRawTextWriter.WriteString(String text)   
在System.Xml.XmlEncodedRawTextWriterIndent.WriteString(String text)   
在System.Xml.XmlWellFormedWriter.WriteString(String text)   
在System.Xml.XmlWriter.WriteElementString(String localName,String ns,String value)   
在System.Xml.Serialization.XmlSerializationWriter.WriteElementString(String localName,String ns,String value,XmlQualifiedName xsiType