Лучший способ кодирования текстовых данных для XML

Я искал универсальный метод в .Net для кодирования строки для использования в элементе или атрибуте Xml и был удивлен, когда я не нашел его сразу. Итак, прежде чем я пойду слишком много дальше, могу ли я просто пропустить встроенную функцию?

Предполагая на мгновение, что это действительно не существует, я собираю свой собственный общий метод EncodeForXml(string data), и я думаю о том, как это сделать.

Данные, которые я использую, которые вызвали все это, могут содержать плохие символы, такие как &, <, ", и т.д. Он также может иногда содержать правильно экранированные объекты: &, & lt;, и", что означает, что просто использование секции CDATA может быть не лучшей идеей. Это похоже на klunky anyay; Я бы скорее получил хорошее строковое значение, которое можно использовать непосредственно в xml.

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

Итак, может ли это быть оптимизировано дальше, не делая его слишком сложным, и есть ли что-нибудь, чего я не вижу?

Function EncodeForXml(ByVal data As String) As String
    Static badAmpersand As new Regex("&(?![a-zA-Z]{2,6};|#[0-9]{2,4};)")

    data = badAmpersand.Replace(data, "&amp;")

    return data.Replace("<", "&lt;").Replace("""", "&quot;").Replace(">", "gt;")
End Function

Извините за все, что вы С# - только люди - мне все равно, какой язык я использую, но я хочу сделать статическое Regex, и вы не можете сделать это на С#, не объявляя его вне метода, поэтому это будет VB.Net

Наконец, мы все еще работаем на .Net 2.0, но если кто-то может взять конечный продукт и превратить его в метод расширения для класса строк, это тоже будет круто.

Обновление. Первые несколько ответов показывают, что .Net действительно имеет встроенные способы сделать это. Но теперь, когда я начал, я как бы хочу закончить свой метод EncodeForXml() только ради удовольствия, поэтому я все еще ищу идеи для улучшения. Примечательно: более полный список символов, которые должны быть закодированы как сущности (возможно, сохранены в списке/карте) и что-то, что имеет лучшую производительность, чем выполнение .Replace() в неизменяемых строках в последовательном интерфейсе.

Ответ 1

System.XML обрабатывает кодировку для вас, поэтому вам не нужен такой метод.

Ответ 2

В зависимости от того, насколько вы знаете о вводе, вам, возможно, придется учитывать, что не все символы Юникода являются действительными символами XML.

Как Server.HtmlEncode, так и System.Security.SecurityElement.Escape, похоже, игнорируют незаконные символы XML, в то время как System.XML.XmlWriter.WriteString генерирует исключение ArgumentException, когда он встречает незаконные символы (если вы не отключите эту проверку, и в этом случае она игнорирует их). Обзор функций библиотеки доступен здесь.

Редактировать 2011/8/14:, увидев, что по крайней мере несколько человек проконсультировались с этим ответом за последние пару лет, я решил полностью переписать исходный код, в котором было много проблем, в том числе ужасно неправильный UTF-16.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

/// <summary>
/// Encodes data so that it can be safely embedded as text in XML documents.
/// </summary>
public class XmlTextEncoder : TextReader {
    public static string Encode(string s) {
        using (var stream = new StringReader(s))
        using (var encoder = new XmlTextEncoder(stream)) {
            return encoder.ReadToEnd();
        }
    }

    /// <param name="source">The data to be encoded in UTF-16 format.</param>
    /// <param name="filterIllegalChars">It is illegal to encode certain
    /// characters in XML. If true, silently omit these characters from the
    /// output; if false, throw an error when encountered.</param>
    public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) {
        _source = source;
        _filterIllegalChars = filterIllegalChars;
    }

    readonly Queue<char> _buf = new Queue<char>();
    readonly bool _filterIllegalChars;
    readonly TextReader _source;

    public override int Peek() {
        PopulateBuffer();
        if (_buf.Count == 0) return -1;
        return _buf.Peek();
    }

    public override int Read() {
        PopulateBuffer();
        if (_buf.Count == 0) return -1;
        return _buf.Dequeue();
    }

    void PopulateBuffer() {
        const int endSentinel = -1;
        while (_buf.Count == 0 && _source.Peek() != endSentinel) {
            // Strings in .NET are assumed to be UTF-16 encoded [1].
            var c = (char) _source.Read();
            if (Entities.ContainsKey(c)) {
                // Encode all entities defined in the XML spec [2].
                foreach (var i in Entities[c]) _buf.Enqueue(i);
            } else if (!(0x0 <= c && c <= 0x8) &&
                       !new[] { 0xB, 0xC }.Contains(c) &&
                       !(0xE <= c && c <= 0x1F) &&
                       !(0x7F <= c && c <= 0x84) &&
                       !(0x86 <= c && c <= 0x9F) &&
                       !(0xD800 <= c && c <= 0xDFFF) &&
                       !new[] { 0xFFFE, 0xFFFF }.Contains(c)) {
                // Allow if the Unicode codepoint is legal in XML [3].
                _buf.Enqueue(c);
            } else if (char.IsHighSurrogate(c) &&
                       _source.Peek() != endSentinel &&
                       char.IsLowSurrogate((char) _source.Peek())) {
                // Allow well-formed surrogate pairs [1].
                _buf.Enqueue(c);
                _buf.Enqueue((char) _source.Read());
            } else if (!_filterIllegalChars) {
                // Note that we cannot encode illegal characters as entity
                // references due to the "Legal Character" constraint of
                // XML [4]. Nor are they allowed in CDATA sections [5].
                throw new ArgumentException(
                    String.Format("Illegal character: '{0:X}'", (int) c));
            }
        }
    }

    static readonly Dictionary<char,string> Entities =
        new Dictionary<char,string> {
            { '"', "&quot;" }, { '&', "&amp;"}, { '\'', "&apos;" },
            { '<', "&lt;" }, { '>', "&gt;" },
        };

    // References:
    // [1] http://en.wikipedia.org/wiki/UTF-16/UCS-2
    // [2] http://www.w3.org/TR/xml11/#sec-predefined-ent
    // [3] http://www.w3.org/TR/xml11/#charsets
    // [4] http://www.w3.org/TR/xml11/#sec-references
    // [5] http://www.w3.org/TR/xml11/#sec-cdata-sect
}

Модульные тесты и полный код можно найти здесь.

Ответ 3

SecurityElement.Escape

documented здесь

Ответ 4

В прошлом я использовал HttpUtility.HtmlEncode для кодирования текста для xml. На самом деле он выполняет ту же задачу. Я еще не столкнулся с какими-либо проблемами, но это не значит, что я не буду в будущем. Как следует из названия, оно было сделано для HTML, а не для XML.

Возможно, вы уже прочитали его, но вот статья о кодировании и декодировании xml.

EDIT: Конечно, если вы используете xmlwriter или один из новых классов XElement, эта кодировка выполняется для вас. Фактически, вы можете просто взять текст, поместить его в новый экземпляр XElement, а затем вернуть строку (.tostring) версию элемента. Я слышал, что SecurityElement.Escape будет выполнять ту же задачу, что и ваш служебный метод, но havent много читал об этом или использовал его.

EDIT2: проигнорируйте мой комментарий о XElement, так как вы все еще на 2.0

Ответ 5

Microsoft библиотека AntiXss Класс AntiXssEncoder в System.Web.dll есть методы для этого:

AntiXss.XmlEncode(string s)
AntiXss.XmlAttributeEncode(string s)

у него также есть HTML:

AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)

Ответ 6

В .net 3.5 +

new XText("I <want> to & encode this for XML").ToString();

Дает вам:

I &lt;want&gt; to &amp; encode this for XML Забастовкa >

Оказывается, этот метод не кодирует некоторые вещи, которые он должен (например, кавычки).

SecurityElement.Escape (ответ workmad3), похоже, лучше справляется с этим и входит в более ранние версии .net.

Если вы не возражаете против стороннего кода и хотите, чтобы никакие незаконные символы не попали в ваш XML, я бы рекомендовал ответить Michael Kropat.

Ответ 7

XmlTextWriter.WriteString() выполняется экранирование.

Ответ 8

Если это приложение ASP.NET, почему бы не использовать Server.HtmlEncode()?

Ответ 9

Это может быть так, когда вы могли бы воспользоваться методом WriteCData.

public override void WriteCData(string text)
    Member of System.Xml.XmlTextWriter

Summary:
Writes out a <![CDATA[...]]> block containing the specified text.

Parameters:
text: Text to place inside the CDATA block.

Простой пример будет выглядеть следующим образом:

writer.WriteStartElement("name");
writer.WriteCData("<unsafe characters>");
writer.WriteFullEndElement();

Результат выглядит так:

<name><![CDATA[<unsafe characters>]]></name>

При чтении значений node XMLReader автоматически удаляет часть CData внутреннего текста, поэтому вам не нужно беспокоиться об этом. Единственный улов - хранить данные как значение innerText для XML node. Другими словами, вы не можете вставлять содержимое CData в значение атрибута.

Ответ 10

Brilliant! Это все, что я могу сказать.

Вот вариант VB обновленного кода (не в классе, просто функция), который очистит, а также дезинформирует xml

Function cXML(ByVal _buf As String) As String
    Dim textOut As New StringBuilder
    Dim c As Char
    If _buf.Trim Is Nothing OrElse _buf = String.Empty Then Return String.Empty
    For i As Integer = 0 To _buf.Length - 1
        c = _buf(i)
        If Entities.ContainsKey(c) Then
            textOut.Append(Entities.Item(c))
        ElseIf (AscW(c) = &H9 OrElse AscW(c) = &HA OrElse AscW(c) = &HD) OrElse ((AscW(c) >= &H20) AndAlso (AscW(c) <= &HD7FF)) _
            OrElse ((AscW(c) >= &HE000) AndAlso (AscW(c) <= &HFFFD)) OrElse ((AscW(c) >= &H10000) AndAlso (AscW(c) <= &H10FFFF)) Then
            textOut.Append(c)
        End If
    Next
    Return textOut.ToString

End Function

Shared ReadOnly Entities As New Dictionary(Of Char, String)() From {{""""c, "&quot;"}, {"&"c, "&amp;"}, {"'"c, "&apos;"}, {"<"c, "&lt;"}, {">"c, "&gt;"}}

Ответ 11

Вы можете использовать встроенный класс XAttribute, который автоматически обрабатывает кодировку:

using System.Xml.Linq;

XDocument doc = new XDocument();

List<XAttribute> attributes = new List<XAttribute>();
attributes.Add(new XAttribute("key1", "val1&val11"));
attributes.Add(new XAttribute("key2", "val2"));

XElement elem = new XElement("test", attributes.ToArray());

doc.Add(elem);

string xmlStr = doc.ToString();

Ответ 12

Вот однолинейное решение, использующее XElements. Я использую его в очень маленьком инструменте. Мне это не нужно во второй раз, поэтому я держу его таким образом. (Его странный дуг)

StrVal = (<x a=<%= StrVal %>>END</x>).ToString().Replace("<x a=""", "").Replace(">END</x>", "")

О, и он работает только в VB не в С#