Как удалить недопустимые шестнадцатеричные символы из источника данных на основе XML до создания XmlReader или XPathDocument, который использует данные? - PullRequest
73 голосов
/ 21 августа 2008

Существует ли какой-либо простой / общий способ очистки источника данных на основе XML перед его использованием в XmlReader, чтобы я мог корректно использовать данные XML, которые не соответствуют ограничениям шестнадцатеричных символов, установленным для XML?

Примечание:

  • Решение должно обрабатывать XML источники данных, которые используют символ кодировки, отличные от UTF-8, например от указав кодировку декларация XML-документа. Не искажение кодировки символов источник во время удаления недействительным шестнадцатеричные символы были главная точка соприкосновения.
  • Удаление недопустимых шестнадцатеричных символов должно удалять только шестнадцатеричные закодированные значения, так как вы часто можете найти значения href в данных, которые содержат строку, которая будет совпадением строки для шестнадцатеричного символа.

Справка:

Мне нужно использовать источник данных на основе XML, который соответствует определенному формату (например, Atom или RSS-каналы), но я хочу иметь возможность использовать опубликованные источники данных, которые содержат недопустимые шестнадцатеричные символы в соответствии со спецификацией XML.

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

Ответы [ 14 ]

73 голосов
/ 21 августа 2008

Это может быть не идеальным (акцент добавлен, так как люди пропускают этот отказ от ответственности), но то, что я сделал в этом случае, ниже. Вы можете настроить для использования с потоком.

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

}
60 голосов
/ 13 марта 2009

Мне нравится концепция белого списка Евгения. Мне нужно было сделать что-то похожее на оригинальный постер, но мне нужно было поддерживать все символы Юникода, а не только до 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();
}
28 голосов
/ 16 февраля 2013

В качестве способа удаления недопустимых символов 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;
    }
}
13 голосов
/ 20 мая 2015

СУХАЯ реализация этого ответа решение (используя другой конструктор - не стесняйтесь использовать тот, который вам нужен в вашем приложении):

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;
    }
}
9 голосов
/ 20 апреля 2012

Модернизация ответа 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.

5 голосов
/ 02 декабря 2014

Вот ответ 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();
    }
}
4 голосов
/ 15 июня 2014

Подход на основе регулярных выражений

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

}

См. Мой блог для более подробной информации

1 голос
/ 26 мая 2017

Модифицированный ответ или оригинальный ответ на Neolisk выше .
Изменения: передано символа \ 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);
        }
    }
1 голос
/ 09 марта 2011

Представленные выше решения, по-видимому, предназначены для удаления недопустимых символов перед преобразованием в 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/

0 голосов
/ 27 января 2019

Я создал слегка обновленную версию из @ ответа Неолиска , которая поддерживает функции *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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...