Обработка строк для вставки в XElement

Мы собираем множество строк и отправляем их нашим клиентам в виде xml-фрагментов. Эти строки могут содержать буквально любой символ. Мы наблюдали ошибку, вызванную попыткой сериализовать экземпляры XElement, содержащие «плохие» символы. Вот пример:

var message = new XElement("song");
char c = (char)0x1a; //sub
var someData = string.Format("some{0}stuff", c);
var attr = new XAttribute("someAttr", someData);
message.Add(attr);
string msgStr = message.ToString(SaveOptions.DisableFormatting); //exception here

Приведенный выше код генерирует исключение в указанной строке. Вот трассировка стека:

'SUB', hexadecimal value 0x1A, is an invalid character. System.ArgumentException System.ArgumentException: '', hexadecimal value 0x1A, is an invalid character.
   at System.Xml.XmlEncodedRawTextWriter.InvalidXmlChar(Int32 ch, Char* pDst, Boolean entitize)
   at System.Xml.XmlEncodedRawTextWriter.WriteAttributeTextBlock(Char* pSrc, Char* pSrcEnd)
   at System.Xml.XmlEncodedRawTextWriter.WriteString(String text)
   at System.Xml.XmlWellFormedWriter.WriteString(String text)
   at System.Xml.XmlWriter.WriteAttributeString(String prefix, String localName, String ns, String value)
   at System.Xml.Linq.ElementWriter.WriteStartElement(XElement e)
   at System.Xml.Linq.ElementWriter.WriteElement(XElement e)
   at System.Xml.Linq.XElement.WriteTo(XmlWriter writer)
   at System.Xml.Linq.XNode.GetXmlString(SaveOptions o)

Я подозреваю, что это неправильное поведение, и плохой символ следует экранировать в XML. Желательно это или нет — вопрос, на который я отвечу позже.

Итак, вот вопрос:

Есть ли какой-то способ обработки строк, чтобы эта ошибка не возникала, или я должен просто удалить все символы ниже char 0x20 и скрестить пальцы?


person spender    schedule 17.10.2012    source источник
comment
Хороший вопрос. На самом деле вы не должны удалять все символы ниже 0x20, потому что некоторые из них должным образом экранированы (например, CR, LF, TAB...). Но я не вижу причин, по которым остальные не сбежали...   -  person Thomas Levesque    schedule 18.10.2012
comment
Будут ли ваши клиенты когда-нибудь нуждаться в этих символах в строках?   -  person Mike Park    schedule 18.10.2012
comment
Нет. Определенно нет. Они либо отображаются в текстовом поле WPF, либо в виде mvcstring в веб-приложении. Фактически, в нашем случае даже комбинации cr/lf/tab @ThomasLevesque могут быть удалены, потому что мы ожидаем одну строку. Эти строки доходят до наших серверов через теги idv3, программное обеспечение для вещания и серверы Shoutcast. Вполне возможно, что кодировки были искажены по пути. Я думаю, что мое исправление полностью применимо для нас. Меня все еще смущает это исключение, и я хотел бы подтвердить, что обнаружил ошибку в .net.   -  person spender    schedule 18.10.2012


Ответы (2)


Это то, что я использую в своем коде:

    static Lazy<Regex> ControlChars = new Lazy<Regex>(() => new Regex("[\x00-\x1f]", RegexOptions.Compiled));

    private static string FixData_Replace(Match match)
    {
        if ((match.Value.Equals("\t")) || (match.Value.Equals("\n")) || (match.Value.Equals("\r")))
            return match.Value;

        return "&#" + ((int)match.Value[0]).ToString("X4") + ";";
    }

    public static string Fix(object data, MatchEvaluator replacer = null)
    {
        if (data == null) return null;
        string fixed_data;
        if (replacer != null) fixed_data = ControlChars.Value.Replace(data.ToString(), replacer);
        else fixed_data = ControlChars.Value.Replace(data.ToString(), FixData_Replace);
        return fixed_data;
    }

Все символы ниже 0x20 (кроме \r \n \t) заменяются их кодами XML Unicode: 0x1f => "f". Синтаксический анализатор XML должен автоматически распаковывать его обратно в 0x1f при чтении файла. Просто используйте новый XAttribute("attribute", Fix(yourString))

Он работает для содержимого XElement и, вероятно, должен работать и для XAttributes.

person Ondra    schedule 18.10.2012
comment
Исправлено с чем-то подобным. За неимением более убедительного ответа я дам вам баллы. - person spender; 19.10.2012

Небольшое исследование ILSpy показало, что можно использовать поле XmlWriter/ReaderSettings.CheckCharacters, чтобы контролировать, будет ли создаваться исключение для недопустимых символов. Заимствуя методы XNode.ToString и XDocument.Parse, я придумал следующие примеры:

Чтобы преобразовать объект XLinq в строку с недопустимыми (управляющими) символами:

XDocument xdoc = XDocument.Parse("<root>foo</root>");
using (StringWriter stringWriter = new StringWriter())
{
    XmlWriterSettings xmlWriterSettings = new XmlWriterSettings { OmitXmlDeclaration = true, CheckCharacters = false };
    using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, xmlWriterSettings))
    {
        xdoc.WriteTo(xmlWriter);
    }

    return stringWriter.ToString();
}

Чтобы проанализировать объект XLinq с недопустимыми символами:

XDocument xdoc;
using (StringReader stringReader = new StringReader(text))
{
    XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { CheckCharacters = false, DtdProcessing = DtdProcessing.Parse, MaxCharactersFromEntities = 10000000L, XmlResolver = null };
    using (XmlReader xmlReader = XmlReader.Create(stringReader, xmlReaderSettings))
    {
        xdoc = XDocument.Load(xmlReader);
    }
}
person Aeon    schedule 13.02.2013