Как удалить недопустимые шестнадцатеричные символы из источника данных на основе XML перед созданием XmlReader или XPathDocument, который использует данные?
есть ли простой / общий способ очистки источника данных на основе XML перед его использованием в XmlReader, чтобы я мог изящно использовать XML-данные, которые не соответствуют шестнадцатеричным ограничениям символов, установленным на XML?
Примечание:
- решение должно обрабатывать XML источники данных, использующие символы кодировки, отличные от UTF-8, например указание кодировки символов на объявление документа XML. Не искажение кодировки символов этот источник при зачистке недействителен шестнадцатеричных символов был главное препятствие.
- удаление недопустимых шестнадцатеричных символов должно удалять только шестнадцатеричные кодированные значения, так как вы часто можете найти значения href в данных, которые содержат строку, которая будет соответствовать строке для шестнадцатеричного символа.
Справочная информация:
Мне нужно использовать источник данных на основе XML, который соответствует определенному формату (думаю, Atom или RSS каналы), но хотите иметь возможность использовать опубликованные источники данных, содержащие недопустимые шестнадцатеричные символы в спецификации XML.
в .NET если у вас есть поток, представляющий источник данных XML, а затем попытаться проанализировать его с помощью XmlReader и/или XPathDocument, возникает исключение из-за включения недопустимых шестнадцатеричных символов в XML-данных. Моя текущая попытка решить эту проблему-проанализировать поток как строку и использовать обычный выражение для удаления и / или замены недопустимых шестнадцатеричных символов, но я ищу более эффективное решение.
13 ответов:
Это не может быть совершенным (акцент добавлен, так как люди пропустили этот отказ от ответственности), но то, что я сделал в этом случае, ниже. Вы можете настроить для использования с потоком.
/// <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(); }
Мне нравится концепция белого списка Юджина. Мне нужно было сделать то же самое, что и оригинальный плакат, но мне нужно было поддерживать все символы Unicode, а не только до 0x00FD. Спецификация XML:
Char = #x9 / #xA / #xD / [#x20 - #xD7FF] / [#xE000-#xFFFD] | [#x10000 - #x10FFFF]
в .NET внутреннее представление символов Юникода составляет всего 16 бит, поэтому мы не можем "разрешить" 0x10000-0x10FFFF явно. Спецификация XML явно запрещает суррогатная код точки, начинающиеся с 0xD800 от появления. Однако возможно, что если мы допустим эти суррогатные кодовые точки в нашем белом списке, кодировка utf-8 наша строка может создать допустимый XML в конце, пока правильная кодировка utf-8 была создана из суррогатных пар символов utf-16 в строке .NET. Я не исследовал это, хотя, поэтому я пошел с более безопасной ставкой и не позволил суррогатам в моем белом списке.
комментарии в решении Евгения вводят в заблуждение, хотя проблема в том, что символы, которые мы исключаем, недопустимы в XML ... они являются вполне допустимыми кодовыми точками Юникода. Мы не удаляем "не-utf-8 символов". Мы удаляем символы utf-8, которые могут не отображаться в хорошо сформированных XML-документах.
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(); }
как способ удаления недопустимых символов XML я предлагаю вам использовать XmlConvert.IsXmlChar метод. Он был добавлен с .NET Framework 4 и также представлен в Silverlight. Вот небольшой пример:
void Main() { string content = "\v\f"; 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; } }
сухая реализации ответ'S решение (с помощью другого конструктора - не стесняйтесь использовать тот, который вам нужен в вашем приложении):
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; } }
модернизация dnewcombe ответ, вы могли бы взять немного упрощенный подход
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)); }
или, с 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()); }
мне было бы интересно узнать, как производительность этих методов сравнивается и как они все сравниваются с подходом черного списка с использованием
Buffer.BlockCopy
.
здесь dnewcomeответ в пользовательском StreamReader. Он просто обертывает реального читателя потока и заменяет символы по мере их чтения.
я реализовал только несколько методов, чтобы сэкономить время. Я использовал это в сочетании с XDocument.Загрузка и поток файлов и только метод Read(char[] buffer, int index, int count) был вызван, поэтому он работал следующим образом. Возможно, вам потребуется реализовать дополнительные методы, чтобы заставить это работать для вашего приложения. Я использовал это подход, потому что он кажется более эффективным, чем другие ответы. Я также реализовал только один из конструкторов, вы, очевидно, можете реализовать любой из конструкторов StreamReader, которые вам нужны, так как это просто проход.
Я решил заменить символы, а не удалять их, потому что это значительно упрощает решение. Таким образом, длина текста остается неизменной, поэтому нет необходимости отслеживать отдельный индекс.
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(); } }
подход на основе регулярных выражений
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, "");
}
посмотреть мои blogpost для более подробной информации
вышеуказанные решения, похоже, предназначены для удаления недопустимых символов перед преобразованием в XML.
используйте этот код для удаления недопустимых символов XML из строки XML. например. &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/
измененный ответ или оригинальный ответ от Neolisk выше.
изменения: символ \0 передается, удаление выполняется, а не замена. кроме того, используется XmlConvert.Метод IsXmlChar (char)/// <summary> /// Replaces invalid Xml characters from input file, NOTE: if replacement character is , 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 ('' == _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 ('' == _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); } }
используйте эту функцию для удаления недопустимых символов xml.
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 ); }