Неуязвимый XMLException - PullRequest
5 голосов
/ 21 марта 2012

Фон

Я сериализовал очень большой 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 #, framework 4, встроенный в VS2010 Pro.
  • Я не могу просмотретьзначение xmlText в режиме отладки, поскольку List<string> слишком велико и в окнах наблюдения просто отображается сообщение об ошибке Unable to evaluate the expression. Not enough storage is available to complete this operation..

Ответы [ 3 ]

8 голосов
/ 20 сентября 2012

Если у вас есть данные, в которых вы сериализовали класс, содержащий символы, которые впоследствии не могут быть десериализованы, вы можете очистить данные следующим способом:

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;
}

Предпочтительное решение состоит в том, чтобы запретить сериализацию недопустимых символов в точке сериализации, как указано в пункте 1 ответа Поргеса. Этот код охватывает пункт 2 ответа Поргеса (удаление ссылок на этот недопустимый символ) и удаляет все недопустимые символы. Приведенный выше код был написан для решения проблемы, когда мы сохранили сериализованные данные в поле базы данных, поэтому необходимо было исправить устаревшие данные, и решение проблемы в точке сериализации не было возможным.

8 голосов
/ 21 марта 2012

Я думаю, что нашел проблему.По умолчанию 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.

2 голосов
/ 15 апреля 2015

Эта проблема также мучает нас при работе с управляющими символами 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, является недопустимым символом.в System.Xml.XmlEncodedRawTextWriter.InvalidXmlChar (Int32 ch, Char * pDst, логическое разрешение) в System.Xml.XmlEncodedRawTextWriter.WriteElementTextBlock (Char * pSrc, Char * pSrcEnd)at System.Xml.XmlEncodedRawTextWriter.WriteString (String text)at System.Xml.XmlEncodedRawTextWriterIndent.WriteString (String text)в System.Xml.XmlWellFormedWriter.WriteString (текст строки)в System.Xml.XmlWriter.WriteElementString (String localName, String ns, String value)в System.Xml.Serialization.XmlSerializationWriter.WriteElementString (String localName, String ns, String value, XmlQualifiedName xsiType

...