c# xml validation encoding

c# - ¿Cómo se eliminan los caracteres hexadecimales inválidos de una fuente de datos basada en XML antes de construir un XmlReader o XPathDocument que utiliza los datos?



validation encoding (13)

¿Existe alguna manera fácil / general de limpiar una fuente de datos basada en XML antes de usarla en un XmlReader, de modo que pueda consumir con gracia datos XML que no se ajusten a las restricciones de caracteres hexadecimales que se colocan en XML?

Nota:

  • La solución necesita manejar orígenes de datos XML que usen codificaciones de caracteres distintas de UTF-8, por ejemplo, especificando la codificación de caracteres en la declaración del documento XML. No alterar la codificación de caracteres de la fuente mientras se eliminan caracteres hexadecimales inválidos ha sido un importante punto de fricción.
  • La eliminación de caracteres hexadecimales inválidos solo debería eliminar valores codificados hexadecimales, ya que a menudo puede encontrar valores href en datos que contienen una cadena que sería una coincidencia de cadena para un carácter hexadecimal.

Fondo:

Necesito consumir una fuente de datos basada en XML que se ajuste a un formato específico (piense en fuentes Atom o RSS), pero desee poder consumir fuentes de datos que se hayan publicado que contengan caracteres hexadecimales no válidos según la especificación XML.

En .NET si tiene un flujo que representa el origen de datos XML, y luego intenta analizarlo con un XmlReader y / o XPathDocument, se genera una excepción debido a la inclusión de caracteres hexadecimales no válidos en los datos XML. Mi intento actual de resolver este problema es analizar el flujo como una cadena y usar una expresión regular para eliminar y / o reemplazar los caracteres hexadecimales no válidos, pero estoy buscando una solución más eficaz.


¡Prueba esto para PHP!

$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);


Aquí está la respuesta de dnewcome en un StreamReader personalizado. Simplemente envuelve un lector de flujo real y reemplaza los caracteres a medida que se leen.

Solo implementé algunos métodos para ahorrarme tiempo. Lo usé junto con XDocument.Load y una secuencia de archivos y solo se invocó el método Read (char [] buffer, int index, int count), así que funcionó así. Es posible que deba implementar métodos adicionales para que esto funcione para su aplicación. Usé este enfoque porque parece más eficiente que las otras respuestas. Además, solo implementé uno de los constructores, obviamente podrías implementar cualquiera de los constructores de StreamReader que necesites, ya que es solo un paso directo.

Elegí reemplazar los caracteres en lugar de eliminarlos porque simplifica enormemente la solución. De esta forma, la longitud del texto permanece igual, por lo que no es necesario realizar un seguimiento de un índice por separado.

public class InvalidXmlCharacterReplacingStreamReader : TextReader { private StreamReader implementingStreamReader; private char replacementCharacter; public InvalidXmlCharacterReplacingStreamReader(Stream stream, char replacementCharacter) { implementingStreamReader = new StreamReader(stream); this.replacementCharacter = replacementCharacter; } public override void Close() { implementingStreamReader.Close(); } public override ObjRef CreateObjRef(Type requestedType) { return implementingStreamReader.CreateObjRef(requestedType); } public void Dispose() { implementingStreamReader.Dispose(); } public override bool Equals(object obj) { return implementingStreamReader.Equals(obj); } public override int GetHashCode() { return implementingStreamReader.GetHashCode(); } public override object InitializeLifetimeService() { return implementingStreamReader.InitializeLifetimeService(); } public override int Peek() { int ch = implementingStreamReader.Peek(); if (ch != -1) { if ( (ch < 0x0020 || ch > 0xD7FF) && (ch < 0xE000 || ch > 0xFFFD) && ch != 0x0009 && ch != 0x000A && ch != 0x000D ) { return replacementCharacter; } } return ch; } public override int Read() { int ch = implementingStreamReader.Read(); if (ch != -1) { if ( (ch < 0x0020 || ch > 0xD7FF) && (ch < 0xE000 || ch > 0xFFFD) && ch != 0x0009 && ch != 0x000A && ch != 0x000D ) { return replacementCharacter; } } return ch; } public override int Read(char[] buffer, int index, int count) { int readCount = implementingStreamReader.Read(buffer, index, count); for (int i = index; i < readCount+index; i++) { char ch = buffer[i]; if ( (ch < 0x0020 || ch > 0xD7FF) && (ch < 0xE000 || ch > 0xFFFD) && ch != 0x0009 && ch != 0x000A && ch != 0x000D ) { buffer[i] = replacementCharacter; } } return readCount; } public override Task<int> ReadAsync(char[] buffer, int index, int count) { throw new NotImplementedException(); } public override int ReadBlock(char[] buffer, int index, int count) { throw new NotImplementedException(); } public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) { throw new NotImplementedException(); } public override string ReadLine() { throw new NotImplementedException(); } public override Task<string> ReadLineAsync() { throw new NotImplementedException(); } public override string ReadToEnd() { throw new NotImplementedException(); } public override Task<string> ReadToEndAsync() { throw new NotImplementedException(); } public override string ToString() { return implementingStreamReader.ToString(); } }


Como la forma de eliminar los caracteres XML no válidos, le sugiero que utilice el método XmlConvert.IsXmlChar . Se agregó desde .NET Framework 4 y también se presenta en Silverlight. Aquí está la pequeña muestra:

void Main() { string content = "/v/f/0"; Console.WriteLine(IsValidXmlString(content)); // False content = RemoveInvalidXmlChars(content); Console.WriteLine(IsValidXmlString(content)); // True } static string RemoveInvalidXmlChars(string text) { char[] validXmlChars = text.Where(ch => XmlConvert.IsXmlChar(ch)).ToArray(); return new string(validXmlChars); } static bool IsValidXmlString(string text) { try { XmlConvert.VerifyXmlChars(text); return true; } catch { return false; } }


Enfoque basado en Regex

public static string StripInvalidXmlCharacters(string str) { var invalidXmlCharactersRegex = new Regex("[^/u0009/u000a/u000d/u0020-/ud7ff/ue000-/ufffd]|([/ud800-/udbff](?![/udc00-/udfff]))|((?<![/ud800-/udbff])[/udc00-/udfff])"); return invalidXmlCharactersRegex.Replace(str, "");

}

Ver mi blogpost para más detalles


Implementación DRY de la solución de esta respuesta (usando un constructor diferente, no dude en usar la que necesita en su aplicación):

public class InvalidXmlCharacterReplacingStreamReader : StreamReader { private readonly char _replacementCharacter; public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter) : base(fileName) { this._replacementCharacter = replacementCharacter; } public override int Peek() { int ch = base.Peek(); if (ch != -1 && IsInvalidChar(ch)) { return this._replacementCharacter; } return ch; } public override int Read() { int ch = base.Read(); if (ch != -1 && IsInvalidChar(ch)) { return this._replacementCharacter; } return ch; } public override int Read(char[] buffer, int index, int count) { int readCount = base.Read(buffer, index, count); for (int i = index; i < readCount + index; i++) { char ch = buffer[i]; if (IsInvalidChar(ch)) { buffer[i] = this._replacementCharacter; } } return readCount; } private static bool IsInvalidChar(int ch) { return (ch < 0x0020 || ch > 0xD7FF) && (ch < 0xE000 || ch > 0xFFFD) && ch != 0x0009 && ch != 0x000A && ch != 0x000D; } }


Las soluciones anteriores parecen ser para eliminar caracteres no válidos antes de convertir a XML.

Utilice este código para eliminar caracteres XML no válidos de una cadena XML. p.ej. & x1A;

public static string CleanInvalidXmlChars( string Xml, string XMLVersion ) { string pattern = String.Empty; switch( XMLVersion ) { case "1.0": pattern = @"&#x((10?|[2-F])FFF[EF]|FDD[0-9A-F]|7F|8[0-46-9A-F]9[0-9A-F]);"; break; case "1.1": pattern = @"&#x((10?|[2-F])FFF[EF]|FDD[0-9A-F]|[19][0-9A-F]|7F|8[0-46-9A-F]|0?[1-8BCEF]);"; break; default: throw new Exception( "Error: Invalid XML Version!" ); } Regex regex = new Regex( pattern, RegexOptions.IgnoreCase ); if( regex.IsMatch( Xml ) ) Xml = regex.Replace( Xml, String.Empty ); return Xml; }

http://balajiramesh.wordpress.com/2008/05/30/strip-illegal-xml-characters-based-on-w3c-standard/


Me gusta el concepto de lista blanca de Eugene. Necesitaba hacer algo similar al póster original, pero tenía que admitir todos los caracteres Unicode, no solo hasta 0x00FD. La especificación XML es:

Char = # x9 | #xA | #xD | [# x20- # xD7FF] | [# xE000- # xFFFD] | [# x10000- # x10FFFF]

En .NET, la representación interna de caracteres Unicode es de solo 16 bits, por lo que no podemos `permitir ''0x10000-0x10FFFF explícitamente. La especificación XML explícitamente no permite que aparezcan los puntos de código sustituto que comienzan en 0xD800. Sin embargo, es posible que si permitimos estos puntos de código sustituto en nuestra lista blanca, la codificación utf-8 de nuestra cadena produzca XML válido al final siempre que se produzca una codificación utf-8 adecuada a partir de los pares de utf-16 en el Cadena .NET No he explorado esto, así que opté por la apuesta más segura y no permití los sustitutos en mi lista blanca.

Los comentarios en la solución de Eugene son engañosos, sin embargo, el problema es que los caracteres que estamos excluyendo no son válidos en XML ... son puntos de código Unicode perfectamente válidos. No estamos eliminando `caracteres que no sean utf-8 ''. Estamos eliminando caracteres utf-8 que pueden no aparecer en documentos XML bien formados.

public static string XmlCharacterWhitelist( string in_string ) { if( in_string == null ) return null; StringBuilder sbOutput = new StringBuilder(); char ch; for( int i = 0; i < in_string.Length; i++ ) { ch = in_string[i]; if( ( ch >= 0x0020 && ch <= 0xD7FF ) || ( ch >= 0xE000 && ch <= 0xFFFD ) || ch == 0x0009 || ch == 0x000A || ch == 0x000D ) { sbOutput.Append( ch ); } } return sbOutput.ToString(); }


Modernizando dnewcombe''s respuesta dnewcombe''s , podría tomar un enfoque un poco más simple

public static string RemoveInvalidXmlChars(string input) { var isValid = new Predicate<char>(value => (value >= 0x0020 && value <= 0xD7FF) || (value >= 0xE000 && value <= 0xFFFD) || value == 0x0009 || value == 0x000A || value == 0x000D); return new string(Array.FindAll(input.ToCharArray(), isValid)); }

o, con Linq

public static string RemoveInvalidXmlChars(string input) { return new string(input.Where(value => (value >= 0x0020 && value <= 0xD7FF) || (value >= 0xE000 && value <= 0xFFFD) || value == 0x0009 || value == 0x000A || value == 0x000D).ToArray()); }

Me interesaría saber cómo se compara el rendimiento de estos métodos y cómo se comparan con un enfoque de lista negra usando Buffer.BlockCopy .


Puede pasar caracteres que no sean UTF con lo siguiente:

string sFinalString = ""; string hex = ""; foreach (char ch in UTFCHAR) { int tmp = ch; if ((ch < 0x00FD && ch > 0x001F) || ch == ''/t'' || ch == ''/n'' || ch == ''/r'') { sFinalString += ch; } else { sFinalString += "&#" + tmp+";"; } }


Respuesta modificada o respuesta original por Neolisk arriba .
Cambios: se pasa el carácter / 0, se realiza la eliminación, en lugar de un reemplazo. también, hizo uso del método XmlConvert.IsXmlChar (char)

/// <summary> /// Replaces invalid Xml characters from input file, NOTE: if replacement character is /0, then invalid Xml character is removed, instead of 1-for-1 replacement /// </summary> public class InvalidXmlCharacterReplacingStreamReader : StreamReader { private readonly char _replacementCharacter; public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter) : base(fileName) { _replacementCharacter = replacementCharacter; } public override int Peek() { int ch = base.Peek(); if (ch != -1 && IsInvalidChar(ch)) { if (''/0'' == _replacementCharacter) return Peek(); // peek at the next one return _replacementCharacter; } return ch; } public override int Read() { int ch = base.Read(); if (ch != -1 && IsInvalidChar(ch)) { if (''/0'' == _replacementCharacter) return Read(); // read next one return _replacementCharacter; } return ch; } public override int Read(char[] buffer, int index, int count) { int readCount= 0, ch; for (int i = 0; i < count && (ch = Read()) != -1; i++) { readCount++; buffer[index + i] = (char)ch; } return readCount; } private static bool IsInvalidChar(int ch) { return !XmlConvert.IsXmlChar((char)ch); } }


Utilice esta función para eliminar caracteres xml no válidos.

public static string CleanInvalidXmlChars(string text) { string re = @"[^/x09/x0A/x0D/x20-/xD7FF/xE000-/xFFFD/x10000-x10FFFF]"; return Regex.Replace(text, re, ""); }


Puede que no sea perfecto (se ha añadido énfasis ya que las personas no tienen esta cláusula de exención de responsabilidad), pero lo que he hecho en ese caso se encuentra a continuación. Puede ajustar para usar con una secuencia.

/// <summary> /// Removes control characters and other non-UTF-8 characters /// </summary> /// <param name="inString">The string to process</param> /// <returns>A string with no control characters or entities above 0x00FD</returns> public static string RemoveTroublesomeCharacters(string inString) { if (inString == null) return null; StringBuilder newString = new StringBuilder(); char ch; for (int i = 0; i < inString.Length; i++) { ch = inString[i]; // remove any characters outside the valid UTF-8 range as well as all control characters // except tabs and new lines //if ((ch < 0x00FD && ch > 0x001F) || ch == ''/t'' || ch == ''/n'' || ch == ''/r'') //if using .NET version prior to 4, use above logic if (XmlConvert.IsXmlChar(ch)) //this method is new in .NET 4 { newString.Append(ch); } } return newString.ToString(); }


private static String removeNonUtf8CompliantCharacters( final String inString ) { if (null == inString ) return null; byte[] byteArr = inString.getBytes(); for ( int i=0; i < byteArr.length; i++ ) { byte ch= byteArr[i]; // remove any characters outside the valid UTF-8 range as well as all control characters // except tabs and new lines if ( !( (ch > 31 && ch < 253 ) || ch == ''/t'' || ch == ''/n'' || ch == ''/r'') ) { byteArr[i]='' ''; } } return new String( byteArr ); }