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

StackOverflow https://stackoverflow.com/questions/157646

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

Данные, которые я использую, которые побудили ко всему этому, могут содержать плохие символы, такие как &, <, " и т.д.Иногда он также может содержать должным образом экранированные объекты:&, <, и ", что означает, что просто использовать раздел CDATA может быть не лучшей идеей.В любом случае, это кажется немного неуклюжим;Я бы предпочел получить хорошее строковое значение, которое можно использовать непосредственно в 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 # - мне действительно все равно, какой язык я использую, но я хотел сделать регулярное выражение статическим, а вы не можете сделать это на C #, не объявив его вне метода, так что это будет VB.Net

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

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

Это было полезно?

Решение

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

Другие советы

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

И то , и другое Сервер.HtmlEncode и Система.Безопасность.Элемент безопасности.Побег кажется, игнорирует недопустимые символы XML, в то время как System.XML.XmlWriter.Строка записи бросает Исключение 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
}

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

Элемент безопасности.Побег

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

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

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

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

РЕДАКТИРОВАТЬ 2:Не обращайте внимания на мой комментарий по поводу XElement, поскольку вы все еще на версии 2.0

Корпорация Майкрософт Библиотека AntiXSS Класс AntiXssEncoder в System.Web.dll есть методы для этого:

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

в нем также есть HTML:

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

В .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, я бы рекомендовал Ответ Майкла Кропата.

XmlTextWriter.WriteString() совершает побег.

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

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

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

Вот вариант обновленного кода на 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;"}}

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top