Development Tip

데이터를 사용하는 XmlReader 또는 XPathDocument를 생성하기 전에 XML 기반 데이터 원본에서 잘못된 16 진수 문자를 어떻게 제거합니까?

yourdevel 2020. 10. 20. 08:13
반응형

데이터를 사용하는 XmlReader 또는 XPathDocument를 생성하기 전에 XML 기반 데이터 원본에서 잘못된 16 진수 문자를 어떻게 제거합니까?


XML에 적용된 16 진수 문자 제한을 준수하지 않는 XML 데이터를 정상적으로 사용할 수 있도록 XmlReader에서 사용하기 전에 XML 기반 데이터 원본을 정리하는 쉽고 일반적인 방법이 있습니까?

노트 :

  • 솔루션은 예를 들어 XML 문서 선언에서 문자 인코딩을 지정하여 UTF-8 이외의 문자 인코딩을 사용하는 XML 데이터 소스를 처리해야합니다. 유효하지 않은 16 진수 문자를 제거하는 동안 소스의 문자 인코딩을 변경하지 않는 것이 주요 문제였습니다.
  • 잘못된 16 진수 문자를 제거하면 16 진수로 인코딩 된 값만 제거해야합니다. 데이터에서 16 진수 문자와 일치하는 문자열이 포함 된 href 값을 종종 찾을 수 있기 때문입니다.

배경:

특정 형식 (Atom 또는 RSS 피드 등)을 준수하는 XML 기반 데이터 원본을 사용해야하지만 XML 사양에 따라 잘못된 16 진수 문자가 포함 된 게시 된 데이터 원본을 사용할 수 있기를 원합니다.

.NET에서 XML 데이터 원본을 나타내는 Stream이있는 경우 XmlReader 및 / 또는 XPathDocument를 사용하여이를 구문 분석하려고하면 XML 데이터에 잘못된 16 진수 문자가 포함되어 예외가 발생합니다. 이 문제를 해결하려는 현재 시도는 Stream을 문자열로 구문 분석하고 정규식을 사용하여 잘못된 16 진수 문자를 제거 및 / 또는 대체하는 것이지만 더 성능이 좋은 솔루션을 찾고 있습니다.


그것은 완벽하지 않을 수 있습니다 (강조는이 면책 조항을 실종자 이후에 추가),하지만 내가 그 경우에 수행 한 것은 다음과 같습니다. 스트림과 함께 사용하도록 조정할 수 있습니다.

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

}

나는 Eugene의 화이트리스트 개념을 좋아합니다. 원본 포스터와 비슷한 작업을해야했지만 0x00FD까지가 아닌 모든 유니 코드 문자를 지원해야했습니다. XML 사양은 다음과 같습니다.

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

.NET에서 유니 코드 문자의 내부 표현은 16 비트에 불과하므로 0x10000-0x10FFFF를 명시 적으로 '허용'할 수 없습니다. XML 사양 은 0xD800에서 시작하는 대리 코드 포인트가 나타나지 않도록 명시 적으로 허용하지 않습니다 . 그러나 화이트리스트에서 이러한 대리 코드 포인트를 허용하면 문자열의 utf-16 문자 대리 쌍에서 적절한 utf-8 인코딩이 생성되는 한 utf-8 인코딩이 결국 유효한 XML을 생성 할 수 있습니다. .NET 문자열. 나는 이것을 탐구하지 않았기 때문에 더 안전한 내기를 걸었고 내 화이트리스트에서 대리자를 허용하지 않았습니다.

Eugene 솔루션의 주석은 오해의 소지가 있지만 문제는 우리가 제외하는 문자가 XML 에서 유효하지 않다는 것입니다. 완벽하게 유효한 유니 코드 코드 포인트입니다. 우리는`utf-8이 아닌 문자 '를 제거하지 않습니다. 올바른 형식의 XML 문서에 나타나지 않을 수있는 utf-8 문자를 제거합니다.

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

이 답변 의 솔루션 의 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;
    }
}

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

이 방법의 성능이 어떻게 비교되는지, 그리고 .NET을 사용하는 블랙리스트 접근 방식과 어떻게 비교되는지 알고 싶습니다 Buffer.BlockCopy.


다음은 사용자 정의 StreamReader의 dnewcome 의 답변입니다. 단순히 실제 스트림 리더를 래핑하고 읽을 때 문자를 대체합니다.

시간을 절약하기 위해 몇 가지 방법 만 구현했습니다. 나는 이것을 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();
    }
}

정규식 기반 접근 방식

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

}

자세한 내용은 내 블로그 게시물 을 참조하십시오.


위의 솔루션은 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 \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);
        }
    }

이 기능을 사용하여 유효하지 않은 xml 문자를 제거하십시오.

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

기능 을 지원 하고 .Net 4.0 기능을 사용하는 @Neolisk의 답변약간 업데이트 된 버전만들었습니다 .*AsyncXmlConvert.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);
    }
}

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

다음을 사용하여 비 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+";";
    }
}

PHP 용으로 사용해보세요!

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

참고URL : https://stackoverflow.com/questions/20762/how-do-you-remove-invalid-hexadecimal-characters-from-an-xml-based-data-source-p

반응형