.net - desventajas - html lang en html5
La mejor forma de codificar datos de texto para XML (13)
Estaba buscando un método genérico en .Net para codificar una cadena para usar en un elemento o atributo Xml, y me sorprendió cuando no encontré uno inmediatamente. Entonces, antes de ir mucho más allá, ¿podría estar perdiendo la función incorporada?
Asumiendo por un momento que realmente no existe, estoy armando mi propio EncodeForXml(string data)
genérico EncodeForXml(string data)
, y estoy pensando en la mejor manera de hacerlo.
Los datos que estoy usando que me sugirieron todo esto podrían contener caracteres incorrectos como &, <, ", etc. También podría contener en ocasiones las entidades correctamente escapadas: & amp ;, & lt ;, & & quot ;, lo que significa que simplemente usa un La sección de CDATA puede no ser la mejor idea. Eso parece algo como klunky, preferiría terminar con un buen valor de cadena que pueda usarse directamente en el xml.
He usado una expresión regular en el pasado para simplemente atrapar símbolos malos, y estoy pensando en usarlos para atraparlos en este caso, así como en el primer paso, y luego hacer un simple reemplazo para otros personajes.
Entonces, ¿podría optimizarse aún más sin hacerlo demasiado complejo, y hay algo que me falta? :
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, "&")
return data.Replace("<", "<").Replace("""", """).Replace(">", "gt;")
End Function
Lo siento por todo lo que C # -sólo amigos- realmente no me importa qué idioma utilizo, pero quería hacer que Regex esté estática y no se puede hacer eso en C # sin declararlo fuera del método, así que esto será VB .Red
Finalmente, todavía estamos en .Net 2.0 donde trabajo, pero si alguien pudiera tomar el producto final y convertirlo en un método de extensión para la clase de cadena, también sería genial.
Actualización Las primeras respuestas indican que .Net sí tiene formas integradas de hacerlo. Pero ahora que he comenzado, quiero terminar mi método EncodeForXml () solo por diversión, así que aún estoy buscando ideas para mejorar. Notablemente: una lista más completa de caracteres que deberían codificarse como entidades (tal vez almacenadas en una lista / mapa), y algo que obtiene un mejor rendimiento que hacer un .Replace () en cadenas inmutables en serie.
¡Brillante! Esto es todo lo que puedo decir.
Aquí hay una variante de VB del código actualizado (no en una clase, solo una función) que limpiará y también desinfectará el 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, """}, {"&"c, "&"}, {"''"c, "'"}, {"<"c, "<"}, {">"c, ">"}}
Aquí hay una solución de línea única que usa XElements. Lo uso en una herramienta muy pequeña. No lo necesito por segunda vez, así que lo mantengo de esta manera. (Es raro doug)
StrVal = (<x a=<%= StrVal %>>END</x>).ToString().Replace("<x a=""", "").Replace(">END</x>", "")
Ah, y solo funciona en VB, no en C #
Dependiendo de cuánto sepa acerca de la entrada, es posible que deba tener en cuenta que no todos los caracteres Unicode son caracteres XML válidos .
Tanto Server.HtmlEncode como System.Security.SecurityElement.Escape parecen ignorar los caracteres XML ilegales, mientras que System.XML.XmlWriter.WriteString arroja una ArgumentException cuando encuentra caracteres ilegales (a menos que deshabilite esa comprobación en cuyo caso los ignora). Una descripción general de las funciones de la biblioteca está disponible here .
Edit 2011/8/14: al ver que al menos algunas personas han consultado esta respuesta en los últimos años, decidí reescribir completamente el código original, que tenía numerosos problemas, incluido el mal manejo de 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> {
{ ''"'', """ }, { ''&'', "&"}, { ''/''', "'" },
{ ''<'', "<" }, { ''>'', ">" },
};
// 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
}
Las pruebas unitarias y el código completo se pueden encontrar here .
En el pasado, he usado HttpUtility.HtmlEncode para codificar texto para xml. Realiza la misma tarea, realmente. Aún no me he encontrado con ningún problema, pero eso no quiere decir que no lo haré en el futuro. Como su nombre lo indica, fue hecho para HTML, no para XML.
Probablemente ya lo haya leído, pero aquí hay un artículo sobre codificación y decodificación xml.
EDITAR: por supuesto, si usa un xmlwriter o una de las nuevas clases de XElement, esta codificación se hace por usted. De hecho, puede tomar el texto, colocarlo en una nueva instancia de XElement y luego devolver la versión de cadena (.tostring) del elemento. He oído que link también realizará la misma tarea que su método de utilidad, pero no ha leído mucho ni lo ha usado.
EDIT2: Haga caso omiso de mi comentario sobre XElement, ya que todavía está en 2.0
Este podría ser el caso donde podría beneficiarse al usar el 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.
Un ejemplo simple se vería así:
writer.WriteStartElement("name");
writer.WriteCData("<unsafe characters>");
writer.WriteFullEndElement();
El resultado se ve así:
<name><![CDATA[<unsafe characters>]]></name>
Al leer los valores de nodo, XMLReader elimina automáticamente la parte de CData del texto interno para que no tenga que preocuparse por ello. La única pega es que tienes que almacenar los datos como un valor de texto interno a un nodo XML. En otras palabras, no puede insertar contenido de CData en un valor de atributo.
La clase AntiXssEncoder Class de Microsoft en System.Web.dll tiene métodos para esto:
AntiXss.XmlEncode(string s)
AntiXss.XmlAttributeEncode(string s)
también tiene HTML:
AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)
Puede usar la clase XAttribute , que maneja la codificación automáticamente:
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();
SecurityElement.Escape
documentado here
Si esta es una aplicación ASP.NET, ¿por qué no usar Server.HtmlEncode ()?
Si realmente quiere manejar todos los caracteres no válidos (no solo los pocos "html") y tiene acceso a System.Xml
, esta es la forma más sencilla de hacer una codificación Xml adecuada de los datos 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  else  <script>alert(''123'');</script>
// Repeat the last 2 lines to escape additional strings.
Es importante saber que XmlConvert.EncodeName()
no es apropiado, porque eso es para nombres de entidad / etiqueta, no valores. Usar eso sería como la codificación Url cuando necesitas codificar Html.
System.XML maneja la codificación por usted, por lo que no necesita un método como este.
XmlTextWriter.WriteString()
hace el escape.
En .net 3.5+
new XText("I <want> to & encode this for XML").ToString();
Te dio:
I <want> to & encode this for XML
Resulta que este método no codifica algunas cosas que debería (como comillas).
SecurityElement.Escape
( la respuesta de workmad3 ) parece hacer un mejor trabajo con esto y está incluido en versiones anteriores de .net.
Si no te importa el código de terceros y quieres asegurarte de que ningún personaje ilegal lo incluya en tu XML, recomendaría la respuesta de Michael Kropat .