Pergunta

Eu estava procurando um método genérico em .Net para codificar uma string para uso em um elemento ou atributo Xml e fiquei surpreso quando não encontrei um imediatamente.Então, antes de ir muito mais longe, poderia estar faltando a função integrada?

Supondo por um momento que isso realmente não existe, estou montando meu próprio genérico EncodeForXml(string data) método, e estou pensando na melhor maneira de fazer isso.

Os dados que estou usando e que levaram a tudo isso podem conter caracteres incorretos como &, <, ", etc.Também pode conter ocasionalmente as entidades com escape adequado:&, <, e ", o que significa que apenas usar uma seção CDATA pode não ser a melhor ideia.Isso parece meio desajeitado de qualquer maneira;Prefiro acabar com um bom valor de string que possa ser usado diretamente no xml.

Eu usei uma expressão regular no passado para capturar apenas e comerciais ruins e estou pensando em usá-la para capturá-los neste caso, bem como na primeira etapa, e depois fazer uma substituição simples para outros caracteres.

Então, isso poderia ser otimizado ainda mais sem torná-lo muito complexo, e há alguma coisa que estou perdendo?:

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

Desculpe por todos vocês, somente C #, pessoal - eu realmente não me importo com qual linguagem eu uso, mas eu queria tornar o Regex estático e você não pode fazer isso em C # sem declará-lo fora do método, então isso será VB .Líquido

Por fim, ainda estamos no .Net 2.0 onde trabalho, mas se alguém pudesse pegar o produto final e transformá-lo em um método de extensão para a classe string, seria muito legal também.

Atualizar As primeiras respostas indicam que o .Net realmente possui maneiras integradas de fazer isso.Mas agora que comecei, quero terminar meu método EncodeForXml() apenas por diversão, então ainda estou procurando ideias para melhorias.Notavelmente:uma lista mais completa de caracteres que devem ser codificados como entidades (talvez armazenados em uma lista/mapa) e algo que obtenha melhor desempenho do que fazer um .Replace() em strings imutáveis ​​​​em serial.

Foi útil?

Solução

System.XML cuida da codificação para você, então você não precisa de um método como este.

Outras dicas

Dependendo de quanto você sabe sobre a entrada, talvez seja necessário levar em consideração que nem todos os caracteres Unicode são caracteres XML válidos.

Ambos Servidor.HtmlEncode e System.Security.SecurityElement.Escape parecem ignorar caracteres XML ilegais, enquanto System.XML.XmlWriter.WriteString lança um ArgumentoException quando encontra caracteres ilegais (a menos que você desabilite essa verificação, caso em que ela os ignora).Uma visão geral das funções da biblioteca está disponível aqui.

Editar 14/08/2011: vendo que pelo menos algumas pessoas consultaram esta resposta nos últimos dois anos, decidi reescrever completamente o código original, que apresentava vários problemas, incluindo manipulando horrivelmente mal o 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
}

Testes de unidade e código completo podem ser encontrados aqui.

SecurityElement.Escape

documentado aqui

No passado, usei HttpUtility.HtmlEncode para codificar texto para xml.Ele executa a mesma tarefa, na verdade.Ainda não tive problemas com isso, mas isso não quer dizer que não terei no futuro.Como o nome indica, foi feito para HTML, não para XML.

Você provavelmente já leu, mas aqui está um artigo na codificação e decodificação xml.

EDITAR:Claro, se você usar um xmlwriter ou uma das novas classes XElement, essa codificação será feita para você.Na verdade, você poderia simplesmente pegar o texto, colocá-lo em uma nova instância de XElement e retornar a versão string (.tostring) do elemento.Eu ouvi isso SecurityElement.Escape também executará a mesma tarefa que seu método utilitário, mas não leu muito sobre ele ou o usou.

EDITAR2:Desconsidere meu comentário sobre o XElement, já que você ainda está no 2.0

da Microsoft Biblioteca AntiXss Classe AntiXssEncoder em System.Web.dll tem métodos para isso:

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

também tem HTML:

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

Em .net 3.5+

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

Da-te:

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

Acontece que esse método não codifica algumas coisas que deveria (como aspas).

SecurityElement.Escape (resposta do workmad3) parece fazer um trabalho melhor com isso e está incluído em versões anteriores do .net.

Se você não se importa com códigos de terceiros e deseja garantir que nenhum caractere ilegal entre em seu XML, eu recomendaria A resposta de Michael Kropat.

XmlTextWriter.WriteString() faz a fuga.

Se este é um aplicativo ASP.NET, por que não usar Server.HtmlEncode() ?

Este pode ser o caso em que você poderia se beneficiar do uso do método 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.

Um exemplo simples seria parecido com o seguinte:

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

O resultado é semelhante a:

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

Ao ler os valores dos nós, o XMLReader remove automaticamente a parte CData do texto interno para que você não precise se preocupar com isso.O único problema é que você precisa armazenar os dados como um valor innerText em um nó XML.Em outras palavras, você não pode inserir conteúdo CData em um valor de atributo.

Brilhante!Isso é tudo o que posso dizer.

Aqui está uma variante VB do código atualizado (não em uma classe, apenas uma função) que irá limpar e também higienizar o 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;"}}

Você pode usar a classe interna Atributo X, que trata a codificação automaticamente:

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();

Aqui está uma solução de linha única usando o XElements.Eu o uso em uma ferramenta muito pequena.Não preciso disso uma segunda vez, então continuo assim.(É sujo, Doug)

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

Ah, e só funciona em VB, não em C#

Se você leva a sério o manuseio todos dos caracteres inválidos (não apenas os poucos "html"), e você terá acesso a System.Xml, aqui está a maneira mais simples de fazer a codificação XML adequada de dados de valor:

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.

É importante saber disso XmlConvert.EncodeName() não é apropriado, porque isso é para nomes de entidades/tags, não para valores.Usar isso seria como codificação de URL quando você precisa codificar em HTML.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top