Есть ли какой-либо простой / общий способ очистить источник данных на основе XML перед его использованием в XmlReader, чтобы я мог изящно использовать данные XML, которые не соответствуют ограничениям шестнадцатеричных символов, наложенным на XML?
Примечание:
Фон:
Мне нужно использовать источник данных на основе XML, который соответствует определенному формату (например, Atom или RSS-каналы), но я хочу иметь возможность использовать опубликованные источники данных, которые содержат недопустимые шестнадцатеричные символы в соответствии со спецификацией XML.
В .NET, если у вас есть Stream, представляющий источник данных XML, а затем вы пытаетесь проанализировать его с помощью XmlReader и / или XPathDocument, возникает исключение из-за включения недопустимых шестнадцатеричных символов в данные XML. Моя текущая попытка решить эту проблему - проанализировать Stream как строку и использовать регулярное выражение для удаления и / или замены недопустимых шестнадцатеричных символов, но я ищу более эффективное решение.





Это может быть не идеальным (курсив добавлен, так как люди пропускают этот отказ от ответственности), но то, что я сделал в этом случае, ниже. Вы можете настроить использование с потоком.
/// <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();
}
попробуйте решение dnewcome ниже.
-1 этот ответ вводит в заблуждение, потому что он удаляет символы, которые действительны в XML, которые не являются управляющими символами и являются допустимыми UTF-8.
@DanielCassidy Вы писали о том, что все это неправильно, но я не вижу вашей публикации с правильным решением. Не могли бы вы опубликовать образец кода, показывающий, как правильно обрабатывать удаление управляющих символов?
@RyanRinaldi У меня есть ответ получше, частично написанный, я постараюсь закончить его и опубликовать в эти выходные. А пока ответ dnewcome является лучшим, если вы готовы согласиться с тем, что он не будет работать должным образом, если ваш XML-код содержит символы из-за пределов базовой многоязычной плоскости. Поскольку Basic Multilingual Plane включает в себя все символы, необходимые для каждого современного естественного языка, это, вероятно, не проблема для вас, хотя в реальных случаях требуются символы за пределами этого диапазона (например, некоторые математические).
Если вы хотите обновить ответ, добавив в него лучший набор фильтров, не стесняйтесь. Как говорится в моем ответе, он может быть не идеальным, но он удовлетворил мои потребности.
Я использовал XmlConvert.IsXmlChar (ch) для своего фильтра.
@BradJ, очень хорошее замечание. Похоже, что этот метод был добавлен в .NET 4, поэтому переключил код на его использование в примере. Спасибо!
Мне нравится концепция белых списков Юджина. Мне нужно было сделать то же самое, что и исходный плакат, но мне нужно было поддерживать все символы Unicode, а не только до 0x00FD. Спецификация XML:
Char = # x9 | #xA | #xD | [# x20- # xD7FF] | [# xE000- # xFFFD] | [# x10000- # x10FFFF]
В .NET внутреннее представление символов Unicode составляет всего 16 бит, поэтому мы не можем явно `разрешить '0x10000-0x10FFFF. Спецификация XML явно запрещает суррогатных кодовых точек, начинающихся с 0xD800, с момента появления. Однако возможно, что, если мы позволим эти суррогатные кодовые точки в нашем белом списке, кодировка utf-8 нашей строки может привести к получению допустимого XML в конце, если правильная кодировка utf-8 была произведена из суррогатных пар символов utf-16 в Строка .NET. Я не исследовал это, поэтому я сделал более безопасную ставку и не разрешил суррогаты в моем белом списке.
Комментарии в решении Юджина вводят в заблуждение, проблема в том, что символы, которые мы исключаем, недействительны в XML ... они являются совершенно допустимыми кодовыми точками Unicode. Мы не удаляем символы, отличные от 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();
}
он добавит &, и это заставит doc = XDocument.Load(@strXMLPath); выдать исключение
привет, как вы думаете, XmlConvert.IsXmlChar () будет более точным? Ответ Евгения изменился с момента вашего последнего комментария. Благодарность
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 );
}
-1 Этот ответ вводит в заблуждение и неверен, потому что он удаляет символы, действительные как в Unicode, так и в XML.
Попробуйте это для PHP!
$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);
Вышеупомянутые решения, похоже, предназначены для удаления недопустимых символов перед преобразованием в 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/
-1 Этот ответ не отвечает на заданный вопрос и в любом случае неверен и вводит в заблуждение, потому что он удаляет только недопустимые ссылки на символы XML, но не недопустимые символы XML.
Вы можете передавать символы, отличные от UTF, с помощью следующего:
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+";";
}
}
-1 Этот ответ неверен, потому что он генерирует недопустимые ссылки на символьные сущности XML (например,  не является допустимой ссылкой на символьные сущности XML). Кроме того, он вводит в заблуждение, поскольку удаляет символы, действительные как в Юникоде, так и в XML.
да, это правда, но приведенное выше решение предназначено для того, если вы хотите передать недопустимый xml в файл xml, чем он будет работать, или вы не можете передать недопустимый символ xml в документе xml
Вы не можете передавать недопустимые символы XML в документ XML, что бы вы ни делали. Например, символ U+0001 START OF HEADING не разрешен в правильно сформированном XML-документе, и даже если вы попытаетесь экранировать его как , это все равно не разрешено в правильно сформированном XML-документе.
Модернизируя ответ dnewcombe's, вы могли бы использовать немного более простой подход
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.
У меня возникла проблема с тем, что метод Linq выдает исключение System.OutOfMemoryException, когда строка XML в больших файлах XML.
@BradJ предположительно, в этих случаях переданная строка очень длинная?
@BradJ, в конечном счете, лучше было бы какое-то преобразование потока, вы могли бы передать его непосредственно в XmlReader.Create, вместо того, чтобы загружать весь файл в строку в памяти.
просто провел тест скорости по сравнению с ответом dnewcombe, и оба ваших решения примерно в 3-4 раза быстрее, причем версия Linq лишь немного медленнее, чем ваша версия без linq. Я не ожидал такой разницы. использовали длинные строки и 100k итераций с секундомером для определения времени.
@Seer Я использую потоки символов длиной ~ 60 тыс., И это решение работает немного медленнее, чем метод StringBuilder, не уверен, что я сделал по-другому.
В качестве способа удаления недопустимых символов XML я предлагаю вам использовать метод XmlConvert.IsXmlChar. Он был добавлен с .NET Framework 4 и также представлен в Silverlight. Вот небольшой образец:
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;
}
}
Подход на основе регулярных выражений
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, "");
}
Смотрите мой Сообщение блога для более подробной информации
Это примерно в 50 раз медленнее, чем решение dnewcome на моей машине.
Вот ответ dnewcome в настраиваемом StreamReader. Он просто обертывает настоящий читатель потока и заменяет символы по мере их чтения.
Я реализовал только несколько методов, чтобы сэкономить время. Я использовал это в сочетании с XDocument.Load и файловым потоком, и был вызван только метод 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();
}
}
В конечном счете, это правильная идея, но ваша реализация может быть DRYer.
@Jodrell: Добавлена СУХАЯ версия здесь.
@Neolisk: Спасибо! Я, наверное, должен был очистить это, прежде чем публиковать :)
DRY реализация решения этот ответ (с использованием другого конструктора - не стесняйтесь использовать тот, который вам нужен в своем приложении):
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;
}
}
может быть, лучше использовать XmlConvert.IsXmlChar () для проверки диапазона ch? Как вы думаете?
@montewhizdoh: IsXmlChar является новым в .NET 4. Если это доступно вам, не стесняйтесь использовать. Это решение .NET 2.0+.
Тот же подход, который я реализовал для себя, но унаследовал от Stream, что было не такой уж хорошей идеей, потому что Stream.Read () работал с массивом байтов, а не с символами, и было не так элегантно проверять символы. Ваше решение, унаследованное от StreamReader, лучше, спасибо!
+1 Потому что это позволяет читать ДЕЙСТВИТЕЛЬНО большие файлы XML (успешно протестировано с файлами размером 100 МБ). Решения, которые загружали все в строку перед фильтрацией плохих символов, завершались ошибками с исключениями OutOfMemory.
Используйте эту функцию для удаления недопустимых символов xml.
public static string CleanInvalidXmlChars(string text)
{
string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]";
return Regex.Replace(text, re, "");
}
Измененный ответ или оригинальный ответ Неолиск выше.
Изменения: передан символ \ 0, производится удаление, а не замена. также использовался метод 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);
}
}
Я создал немного обновленная версия из @ Ответ Неолиска, который поддерживает функции *Async и использует функцию .Net 4.0 XmlConvert.IsXmlChar.
public class InvalidXmlCharacterReplacingStreamReader : StreamReader
{
private readonly char _replacementCharacter;
public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter) : base(fileName)
{
_replacementCharacter = replacementCharacter;
}
public InvalidXmlCharacterReplacingStreamReader(Stream stream, char replacementCharacter) : base(stream)
{
_replacementCharacter = replacementCharacter;
}
public override int Peek()
{
var ch = base.Peek();
if (ch != -1 && IsInvalidChar(ch))
{
return _replacementCharacter;
}
return ch;
}
public override int Read()
{
var ch = base.Read();
if (ch != -1 && IsInvalidChar(ch))
{
return _replacementCharacter;
}
return ch;
}
public override int Read(char[] buffer, int index, int count)
{
var readCount = base.Read(buffer, index, count);
ReplaceInBuffer(buffer, index, readCount);
return readCount;
}
public override async Task<int> ReadAsync(char[] buffer, int index, int count)
{
var readCount = await base.ReadAsync(buffer, index, count).ConfigureAwait(false);
ReplaceInBuffer(buffer, index, readCount);
return readCount;
}
private void ReplaceInBuffer(char[] buffer, int index, int readCount)
{
for (var i = index; i < readCount + index; i++)
{
var ch = buffer[i];
if (IsInvalidChar(ch))
{
buffer[i] = _replacementCharacter;
}
}
}
private static bool IsInvalidChar(int ch)
{
return IsInvalidChar((char)ch);
}
private static bool IsInvalidChar(char ch)
{
return !XmlConvert.IsXmlChar(ch);
}
}
Он не поймал 0x3c, одно из моих приложений считает, что его шестнадцатеричный, я использую ibex