prosource

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

probook 2023. 10. 30. 21:03
반응형

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

XML Reader에서 XML 기반 데이터 소스를 사용하기 전에 XML에 지정된 16진수 문자 제한에 맞지 않는 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();

}

저는 유진의 화이트리스트 컨셉을 좋아합니다.원래 포스터와 비슷한 작업을 해야 했지만, 최대 0x00이 아닌 모든 유니코드 문자를 지원해야 했습니다.FD. XML 사양은 다음과 같습니다.

Char = #x9 | #xA | #xD | [#x20-#xD7]FF] | [#xE000-#xFFFD] | [#x10000-#x10]FFFF]

.NET에서 유니코드 문자의 내부 표현은 16비트에 불과하므로 0x10000-0x10을 '허용'할 수 없습니다.FFFF 명시적으로.XML 규격은 0xD800에서 시작하는 대리 코드 포인트가 나타나지 않도록 명시적으로 허용합니다.그러나 화이트리스트에서 이러한 대리 코드 포인트를 허용한 경우, 최종적으로 의 utf-16 문자의 대리 쌍에서 적절한 utf-8 인코딩이 생성되는 한, utf-8 인코딩이 유효한 XML을 생성할 수 있습니다.NET 문자열.하지만 저는 이것을 탐구하지 않았기 때문에 더 안전한 내기로 가서 화이트리스트에 대리인을 허용하지 않았습니다.

유진의 솔루션에 있는 코멘트는 오해의 소지가 있지만, 문제는 우리가 제외하고 있는 문자가 XML에서 유효하지 않다는 것입니다.완벽하게 유효한 유니코드 코드 포인트입니다.우리는 'non-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;
    }
}

뉴컴의 답변을 현대화하면 좀 더 간단한 접근을 할 수 있습니다.

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.

맞춤형 StreamReader에 대한 답은 다음과 같습니다.이것은 단순히 실제 스트림 리더를 감싸고 글자가 읽히는 대로 대체합니다.

저는 시간을 절약하기 위해 몇 가지 방법을 사용했을 뿐입니다.X Document와 함께 사용하였습니다.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();
    }
}

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

}

자세한 내용은 내 블로그 게시물 보기

나는 @Neolisk의 답변약간 업데이트된 버전을 만들었고, 이 버전은 다음을 지원합니다.*Async를 기능하고 사용합니다.넷 4.0XmlConvert.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);
    }
}

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

반응형