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

StackOverflow https://stackoverflow.com/questions/20762

  •  09-06-2019
  •  | 
  •  

문제

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

메모:

  • 솔루션은 UTF-8 이외의 문자 인코딩을 사용하는 XML 데이터 소스를 처리해야합니다.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();

}

다른 팁

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

char = #x9 | #xa | #xd | [#x20-#xd7ff] | [#xe000-#xfffd] | [#x10000-#x10ffff

.NET에서 유니코드 문자의 내부 표현은 16비트에 불과하므로 0x10000-0x10FFFF를 명시적으로 '허용'할 수 없습니다.명시적으로 XML 사양 불허하다 0xD800에서 시작하는 대리 코드 포인트가 표시되지 않습니다.그러나 화이트리스트에서 이러한 대리 코드 포인트를 허용하면 문자열의 utf-8 인코딩이 utf-16 문자의 대리 쌍에서 적절한 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());
}

이러한 방법의 성능이 어떻게 비교되고 모두 블랙리스트 접근 방식과 어떻게 비교되는지 알고 싶습니다. Buffer.BlockCopy.

여기는 새로 왔어사용자 정의 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();
    }
}

정규식 기반 접근 방식

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/

수정된 답변 또는 원본 답변: 위의 네오리스크.
변경사항:\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, "");   
} 

내가 만들었다 약간 업데이트된 버전 ~의 @Neolisk의 답변, 이는 *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);
    }
}
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);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top