it-swarm.com.ru

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

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

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

Данные, которые я использую, которые запрашивают все это, могут содержать недопустимые символы, такие как &, <, "и т.д. Иногда они могут также содержать правильно экранированные сущности: & amp ;, & lt; и & quot ;, что означает просто использование Раздел CDATA, возможно, не самая лучшая идея. В любом случае, это кажется немного глупым, я бы предпочел получить значение строки Nice, которое можно использовать непосредственно в 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

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

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

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

67
Joel Coehoorn

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

3
MusiGenesis

В зависимости от того, сколько вы знаете о входных данных, вам, возможно, придется принять во внимание, что не все символы Юникода являются действительными символами 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
}

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

76
Michael Kropat

SecurityElement.Escape

задокументировано здесь

31
workmad3

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

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

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

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

26
Kilhoffer

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

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

он также имеет HTML:

AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)
14
Luke Quinane

В .net 3.5+

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

Дает тебе:

I &lt;want&gt; to &amp; encode this for XML

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

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

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

12
Ronnie Overby

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

5
GSerg

Это может быть тот случай, когда вы могли бы извлечь выгоду из использования метода 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>

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

3
Dscoduc

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

3
Kev

Вы можете использовать встроенный класс 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();
0
Cosmin

Если вы серьезно относитесь к обработке всех недопустимых символов (не только нескольких "html"), и у вас есть доступ к System.Xml, вот самый простой способ сделать правильное кодирование Xml значение данных:

string theTextToEscape = "Something \x1d else \x1D <script>alert('123');</script>";
var x = new XmlDocument();
x.LoadXml("<r/>"); // simple, empty root element
x.DocumentElement.InnerText = theTextToEscape; // put in raw string
string escapedText = x.DocumentElement.InnerXml; // Returns:  Something &#x1D; else &#x1D; &lt;script&gt;alert('123');&lt;/script&gt;

// Repeat the last 2 lines to escape additional strings.

Важно знать, что XmlConvert.EncodeName() не подходит, потому что это для имен сущностей/тегов, а не значений. Использование этого было бы похоже на Url-кодирование, когда вам нужно Html-кодирование.

0
Granger

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

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

Да, и это работает только в VB, а не в C #

0
Phillip

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;"}}
0
nepaluz