Come si rimuovono i caratteri esadecimali non validi da un’origine dati basata su XML prima di build un XmlReader o XPathDocument che utilizza i dati?

C’è un modo semplice / generale per pulire un’origine dati basata su XML prima di usarla in un XmlReader in modo da poter usare con garbo dati XML che non sono conformi alle restrizioni di caratteri esadecimali posti su XML?

Nota:

  • La soluzione deve gestire sorgenti di dati XML che utilizzano codifiche di caratteri diverse da UTF-8, ad esempio specificando la codifica dei caratteri nella dichiarazione del documento XML. La mancata manipolazione della codifica dei caratteri dell’origine mentre si rimuovevano caratteri esadecimali non validi è stato un importante punto critico.
  • La rimozione dei caratteri esadecimali non validi dovrebbe rimuovere solo i valori codificati esadecimali, poiché spesso è ansible trovare i valori href nei dati che contiene una stringa che sarebbe una corrispondenza di stringa per un carattere esadecimale.

Sfondo:

Ho bisogno di consumare un’origine dati basata su XML conforms a un formato specifico (pensa Atom o feed RSS), ma voglio essere in grado di consumare fonti di dati che sono state pubblicate che contengono caratteri esadecimali non validi secondo la specifica XML.

In .NET se si dispone di un stream che rappresenta l’origine dati XML e si tenta di analizzarlo utilizzando un XmlReader e / o XPathDocument, viene generata un’eccezione a causa dell’inclusione di caratteri esadecimali non validi nei dati XML. Il mio attuale tentativo di risolvere questo problema è analizzare il stream come una stringa e usare un’espressione regolare per rimuovere e / o sostituire i caratteri esadecimali non validi, ma sto cercando una soluzione più performante.

Potrebbe non essere perfetto (enfasi aggiunta dal momento che le persone non hanno rispettato questo disclaimer), ma quello che ho fatto in quel caso è qui sotto. È ansible regolare l’utilizzo con un stream.

///  /// Removes control characters and other non-UTF-8 characters ///  /// The string to process /// A string with no control characters or entities above 0x00FD 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(); } 

Mi piace il concetto di whitelist di Eugene. Avevo bisogno di fare qualcosa di simile al poster originale, ma avevo bisogno di supportare tutti i caratteri Unicode, non solo fino a 0x00FD. Le specifiche XML sono:

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

In .NET, la rappresentazione interna dei caratteri Unicode ha solo 16 bit, quindi non possiamo “consentire” 0x10000-0x10FFFF in modo esplicito. La specifica XML non consente esplicitamente che i punti del codice surrogato inizino a 0xD800 dall’apparire. Tuttavia è ansible che se permettessimo questi punti di codice surrogato nella nostra whitelist, la codifica utf-8 della nostra stringa potrebbe produrre XML valido alla fine fintanto che la codifica utf-8 corretta fosse prodotta dalle coppie sostitutive di utf-16 caratteri nel Stringa .NET. Non ho esplorato questo però, quindi sono andato con la scommessa più sicura e non ho permesso ai surrogati nella mia whitelist.

I commenti nella soluzione di Eugene sono fuorvianti, però, il problema è che i caratteri che stiamo escludendo non sono validi in XML … sono punti di codice Unicode perfettamente validi. Non stiamo rimuovendo `caratteri non-utf-8 ‘. Stiamo rimuovendo i caratteri utf-8 che potrebbero non apparire in documenti XML ben formati.

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

Come metodo per rimuovere caratteri XML non validi ti suggerisco di utilizzare il metodo XmlConvert.IsXmlChar . È stato aggiunto da .NET Framework 4 e viene presentato anche in Silverlight. Ecco il piccolo campione:

 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; } } 

Implementazione DRY della soluzione di questa risposta (utilizzando un costruttore diverso – sentiti libero di usare quello che ti serve nella tua applicazione):

 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; } } 

Modernizzando la risposta di dnewcombe , potresti adottare un approccio leggermente più semplice

 public static string RemoveInvalidXmlChars(string input) { var isValid = new Predicate(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()); } 

Sarei interessato a sapere come si confrontano le prestazioni di questi metodi e come si confrontano tutti con un approccio lista nera utilizzando Buffer.BlockCopy .

Ecco la risposta di dnewcome in uno StreamReader personalizzato. Semplicemente avvolge un vero lettore di stream e sostituisce i caratteri man mano che vengono letti.

Ho solo implementato alcuni metodi per risparmiare tempo. L’ho usato in congiunzione con XDocument.Load e un stream di file e solo il metodo Read (char [] buffer, int index, int count) è stato chiamato, quindi ha funzionato in questo modo. Potrebbe essere necessario implementare metodi aggiuntivi per farlo funzionare per la tua applicazione. Ho usato questo approccio perché sembra più efficiente delle altre risposte. Ho anche implementato solo uno dei costruttori, potresti ovviamente implementare uno qualsiasi dei costruttori StreamReader di cui hai bisogno, dato che è solo un passaggio.

Ho scelto di sostituire i caratteri anziché rimuoverli perché semplifica enormemente la soluzione. In questo modo la lunghezza del testo rimane la stessa, quindi non è necessario tenere traccia di un indice separato.

 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 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 ReadBlockAsync(char[] buffer, int index, int count) { throw new NotImplementedException(); } public override string ReadLine() { throw new NotImplementedException(); } public override Task ReadLineAsync() { throw new NotImplementedException(); } public override string ReadToEnd() { throw new NotImplementedException(); } public override Task ReadToEndAsync() { throw new NotImplementedException(); } public override string ToString() { return implementingStreamReader.ToString(); } } 

Approccio basato su 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, ""); 

}

Vedi il mio blogpost per maggiori dettagli

Le soluzioni di cui sopra sembrano essere per la rimozione di caratteri non validi prima della conversione in XML.

Utilizzare questo codice per rimuovere caratteri XML non validi da una stringa XML. per esempio. & 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/

Risposta modificata o risposta originale di Neolisk sopra .
Cambiamenti: di \ 0 carattere è passato, la rimozione è fatta, piuttosto che una sostituzione. inoltre, fatto uso del metodo XmlConvert.IsXmlChar (char)

  ///  /// 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 ///  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); } } 

Utilizzare questa funzione per rimuovere caratteri xml non validi.

 public static string CleanInvalidXmlChars(string text) { string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]"; return Regex.Replace(text, re, ""); } 
 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 ); } 

È ansible passare caratteri non UTF con il seguente:

 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+";"; } } 

Prova questo per PHP!

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