XmlTextWriter неправильно пишет управляющие символы

.NET XmlTextWriter создает недопустимые файлы xml.

В XML допустимы некоторые управляющие символы, такие как "горизонтальная вкладка" (	), но другие не такие, как "вертикальная вкладка" (). (См. spec.)

У меня есть строка, которая содержит символ управления UTF-8, который не разрешен в XML.
Хотя XmlTextWriter ускользает от символа, результирующий XML, конечно же, недействителен.

Как я могу убедиться, что XmlTextWriter никогда не создает незаконный XML файл?

Или, если это невозможно сделать с помощью XmlTextWriter, как я могу удалить определенные символы управления, которые не допускаются в XML из строки?

Пример кода:

using (XmlTextWriter writer =
  new XmlTextWriter("test.xml", Encoding.UTF8))
{
  writer.WriteStartDocument();
  writer.WriteStartElement("Test");
  writer.WriteValue("hello \xb world");
  writer.WriteEndElement();
  writer.WriteEndDocument();
}

Вывод:

<?xml version="1.0" encoding="utf-8"?><Test>hello &#xB; world</Test>

Ответы

Ответ 1

Эта документация о поведении скрыта в документации метода WriteString, но похоже, что она применима ко всему классу.

Поведение XmlWriter по умолчанию, созданное с помощью Create, - это бросить исключение ArgumentException при попытке записать значения символов в диапазон 0x-0x1F (исключая символы пробела 0x9, 0xA и 0xD). Эти недопустимые символы XML можно написать, создав XmlWriter с свойством CheckCharacters установлено значение false. Это приведет к при замене символов числовыми символьными сущностями (&#0;через &#0x1F). Кроме того, созданный XmlTextWriter с новым оператор заменит недопустимые символы числовым символом по умолчанию.

Похоже, вы в конечном итоге пишете недопустимые символы, потому что используете класс XmlTextWriter. Лучшим решением для вас было бы использовать XmlWriter Class.

Ответ 2

Просто нашел этот вопрос, когда я боролся с одной и той же проблемой, и я решил решить его с помощью регулярного выражения:

return Regex.Replace(s, @"[\u0000-\u0008\u000B\u000C\u000E-\u001F]", "");

Надеюсь, что это поможет кому-то в качестве альтернативного решения.

Ответ 3

Встроенные .NET-escapers, такие как SecurityElement.Escape, не будут правильно выходить и разбиваться.

  • Вы можете установить CheckCharacters на false как на писателя, так и на читателя, если ваше приложение является единственным, взаимодействующим с файлом. Полученный XML файл по-прежнему будет технически недействительным.

См:

XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = new UTF8Encoding(false);
xmlWriterSettings.CheckCharacters = false;
var sb = new StringBuilder();
var w = XmlWriter.Create(sb, xmlWriterSettings);
w.WriteStartDocument();
w.WriteStartElement("Test");
w.WriteString("hello \xb world");
w.WriteEndElement();
w.WriteEndDocument();
w.Close();
var xml = sb.ToString();
  • Если параметр CheckCharacters - true (по умолчанию он является) слишком строгим, поскольку он просто выдает исключение альтернативным подходом, который более мягким для недействительных символов XML должен был бы просто разделить их:

Googling немного дал белый список XmlTextEncoder, но он также удалит DEL, а другие в диапазоне U + 007F-U +0084, U + 0086-U + 009F, которые согласно Valid XML Characters в википедии действительны только в определенных контекстах и ​​которые RFC упоминает как обескураженные но все еще действительные символы.

public static class XmlTextExtentions
{
    private static readonly Dictionary<char, string> textEntities = new Dictionary<char, string> {
        { '&', "&amp;"}, { '<', "&lt;" }, { '>', "&gt;" }, 
        { '"', "&quot;" }, { '\'', "&apos;" }
    };
    public static string ToValidXmlString(this string str)
    {
        var stripped = str
            .Select((c,i) => new 
            { 
                c1 = c, 
                c2 = i + 1 < str.Length ? str[i+1]: default(char),
                v = XmlConvert.IsXmlChar(c),
                p = i + 1 < str.Length ? XmlConvert.IsXmlSurrogatePair(str[i + 1], c) : false,
                pp = i > 0 ? XmlConvert.IsXmlSurrogatePair(c, str[i - 1]) : false
            })
            .Aggregate("", (s, c) => {                  
                if (c.pp)
                    return s;
                if (textEntities.ContainsKey(c.c1))
                    s += textEntities[c.c1];
                else if (c.v)
                    s += c.c1.ToString();
                else if (c.p)
                    s += c.c1.ToString() + c.c2.ToString();
                return s;
            });
        return stripped;
    }
}

Это передает все тесты XmlTextEncoder, за исключением того, который ожидает, что он разделит DEL, который XmlConvert.IsXmlChar, Википедия и метки спецэффектов являются допустимым (хотя и обескураженным).