76

XmlReader で使用する前に XML ベースのデータ ソースをクリーンアップして、XML に課せられた 16 進文字の制限に準拠していない XML データを適切に使用できるようにする簡単で一般的な方法はありますか?

ノート:

  • このソリューションでは、UTF-8 以外の文字エンコーディングを使用する XML データ ソースを処理する必要があります (たとえば、XML ドキュメント宣言で文字エンコーディングを指定するなど)。無効な 16 進文字を削除する際に、ソースの文字エンコーディングを変更しないことが、大きな問題となっています。
  • 無効な 16 進数文字の削除では、16 進数でエンコードされた値のみを削除する必要があります。これは、たまたま 16 進数文字に一致する文字列を含むデータに href 値が含まれていることがよくあるためです。

バックグラウンド:

特定の形式 (Atom フィードや RSS フィードなど) に準拠する XML ベースのデータ ソースを使用する必要がありますが、XML 仕様に従って無効な 16 進数文字を含む公開済みのデータ ソースを使用できるようにしたいと考えています。

.NET では、XML データ ソースを表す Stream があり、XmlReader や XPathDocument を使用してそれを解析しようとすると、XML データに無効な 16 進文字が含まれているために例外が発生します。この問題を解決するための現在の試みは、ストリームを文字列として解析し、正規表現を使用して無効な 16 進数文字を削除または置換することですが、よりパフォーマンスの高いソリューションを探しています。

4

14 に答える 14

78

完璧ではないかもしれません(人々がこの免責事項を見逃しているため、強調を追加しました)、その場合に私が行ったことは以下のとおりです。ストリームで使用するように調整できます。

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

}
于 2008-08-21T18:50:58.553 に答える
60

Eugene のホワイトリストのコンセプトが気に入っています。元のポスターと同様のことを行う必要がありましたが、0x00FD までだけでなく、すべての Unicode 文字をサポートする必要がありました。XML 仕様は次のとおりです。

文字 = #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

.NET では、Unicode 文字の内部表現は 16 ビットしかないため、0x10000 ~ 0x10FFFF を明示的に「許可」することはできません。XML 仕様では、0xD800 で始まるサロゲート コード ポイントの出現を明示的に禁止しています。ただし、ホワイトリストでこれらのサロゲート コード ポイントを許可した場合、文字列内の utf-16 文字のサロゲート ペアから適切な utf-8 エンコーディングが生成されている限り、文字列を utf-8 でエンコードすると最終的に有効な XML が生成される可能性があります。 .NET 文字列。ただし、これについては調べていないので、より安全な方法を選択し、ホワイトリストにサロゲートを許可しませんでした。

ただし、Eugene のソリューションのコメントは誤解を招きます。問題は、除外する文字がXMLで有効でないことです...それらは完全に有効な Unicode コード ポイントです。「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();
}
于 2009-03-13T06:12:16.963 に答える
32

無効な 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;
    }
}
于 2013-02-16T17:15:32.567 に答える
13

この回答のソリューションの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;
    }
}
于 2015-05-20T13:35:54.227 に答える
9

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.

于 2012-04-20T10:16:07.880 に答える
5

カスタム StreamReader でのdnewcomeの回答は次のとおりです。単純に実際のストリーム リーダーをラップし、読み取った文字を置き換えます。

時間を節約するためにいくつかの方法を実装しただけです。これを XDocument.Load とファイル ストリームと組み合わせて使用​​し、Read(char[] buffer, int index, int count) メソッドのみが呼び出されたので、このように動作しました。これをアプリケーションで機能させるには、追加のメソッドを実装する必要がある場合があります。他の回答よりも効率的だと思われるため、このアプローチを使用しました。また、コンストラクターの 1 つだけを実装しました。これは単なるパススルーであるため、必要な 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();
    }
}
于 2014-12-01T23:50:14.713 に答える
4

正規表現ベースのアプローチ

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

}

詳細については、私のブログ投稿を参照してください

于 2014-06-15T01:01:27.653 に答える
2

上記の解決策は、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/

于 2011-03-08T23:14:01.733 に答える
0

この関数を使用して、無効な xml 文字を削除します。

public static string CleanInvalidXmlChars(string text)   
{   
       string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]";   
       return Regex.Replace(text, re, "");   
} 
于 2016-11-21T12:17:21.093 に答える
-1

以下を使用して、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+";";
    }
}
于 2011-05-09T11:20:37.773 に答える
-1
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 );
}
于 2010-02-18T23:49:13.727 に答える
-5

PHPでこれを試してください!

$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);
于 2010-08-17T15:10:12.940 に答える