Лучший способ кодировать текстовые данные для XML - PullRequest
67 голосов
/ 01 октября 2008

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

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

Данные, которые я использую, которые запрашивали все это, могли содержать недопустимые символы, такие как &, <, "и т. Д. Иногда они могли также содержать правильно экранированные сущности: & amp ;, & lt; и & quot ;, что означает просто использование раздела CDATA может быть не самой лучшей идеей. В любом случае, это кажется немного хитрым, я бы предпочел получить хорошее строковое значение, которое можно использовать непосредственно в xml. </p>

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

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

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 () для неизменяемых строк в последовательном соединении.

Ответы [ 13 ]

76 голосов
/ 09 апреля 2009

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

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

31 голосов
/ 01 октября 2008

SecurityElement.Escape

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

26 голосов
/ 01 октября 2008

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

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

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

EDIT2: Не обращайте внимания на мой комментарий о XElement, так как вы все еще используете 2.0

14 голосов
/ 29 августа 2009

Microsoft Библиотека AntiXss Класс AntiXssEncoder в System.Web.dll имеет следующие методы:

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

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

AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)
12 голосов
/ 22 февраля 2012

In .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 ( ответ workmad3 ), кажется, справляется с этим лучше, и он включен в более ранние версии .net.

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

5 голосов
/ 01 октября 2008

XmlTextWriter.WriteString() убегает.

3 голосов
/ 07 января 2009

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

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

3 голосов
/ 01 октября 2008

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

0 голосов
/ 19 марта 2018

Если вы серьезно относитесь к обработке всех недопустимых символов (не только нескольких "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-кодирование.

...