Wie entfernen Sie ungültige Hexadezimalzeichen aus einer XML-basierten Datenquelle, bevor Sie ein XmlReader- oder XPathDocument erstellen, das die Daten verwendet?

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

  •  09-06-2019
  •  | 
  •  

Frage

Gibt es eine einfache/allgemeine Möglichkeit, eine XML-basierte Datenquelle vor der Verwendung in einem XmlReader zu bereinigen, damit ich XML-Daten, die nicht den für XML geltenden Beschränkungen für hexadezimale Zeichen entsprechen, ordnungsgemäß verarbeiten kann?

Notiz:

  • Die Lösung muss XML-Datenquellen verarbeiten, die andere Zeichencodierungen als UTF-8 verwenden, z. B.Durch Angeben der Charaktercodierung bei der XML -Dokumenterklärung.Es war ein wichtiger Knackpunkt, die Charakter -Codierung der Quelle nicht zu bestreiten, während sie ungültige hexadezimale Merkmale entfernen.
  • Durch das Entfernen ungültiger hexadezimaler Zeichen sollten nur hexadezimal codierte Werte entfernt werden, da Sie häufig href-Werte in Daten finden, die zufällig eine Zeichenfolge enthalten, die mit einem hexadezimalen Zeichen übereinstimmt.

Hintergrund:

Ich muss eine XML-basierte Datenquelle nutzen, die einem bestimmten Format entspricht (denken Sie an Atom- oder RSS-Feeds), möchte aber in der Lage sein, veröffentlichte Datenquellen zu nutzen, die gemäß der XML-Spezifikation ungültige Hexadezimalzeichen enthalten.

Wenn Sie in .NET über einen Stream verfügen, der die XML-Datenquelle darstellt, und dann versuchen, ihn mit einem XmlReader und/oder XPathDocument zu analysieren, wird eine Ausnahme ausgelöst, da die XML-Daten ungültige Hexadezimalzeichen enthalten.Mein aktueller Versuch, dieses Problem zu lösen, besteht darin, den Stream als Zeichenfolge zu analysieren und einen regulären Ausdruck zu verwenden, um die ungültigen Hexadezimalzeichen zu entfernen und/oder zu ersetzen. Ich suche jedoch nach einer leistungsfähigeren Lösung.

War es hilfreich?

Lösung

Es möglicherweise nicht perfekt (Hervorhebung hinzugefügt, da dieser Haftungsausschluss fehlt), aber was ich in diesem Fall getan habe, ist unten aufgeführt.Sie können die Verwendung mit einem Stream anpassen.

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

}

Andere Tipps

Mir gefällt Eugenes Whitelist-Konzept.Ich musste etwas Ähnliches tun wie beim Originalposter, aber ich musste alle Unicode-Zeichen unterstützen, nicht nur bis zu 0x00FD.Die XML-Spezifikation lautet:

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

In .NET beträgt die interne Darstellung von Unicode-Zeichen nur 16 Bit, daher können wir 0x10000-0x10FFFF nicht explizit „zulassen“.Die XML-Spezifikation explizit verbietet Die Ersatzcodepunkte beginnend bei 0xD800 werden nicht angezeigt.Es ist jedoch möglich, dass, wenn wir diese Ersatzcodepunkte in unserer Whitelist zulassen, die UTF-8-Kodierung unserer Zeichenfolge am Ende gültiges XML erzeugen könnte, solange aus den Ersatzpaaren von UTF-16-Zeichen in der richtigen UTF-8-Kodierung erzeugt wurde .NET-Zeichenfolge.Da ich das allerdings noch nicht untersucht habe, habe ich mich für die sicherere Variante entschieden und die Surrogate nicht in meine Whitelist aufgenommen.

Die Kommentare in Eugenes Lösung sind jedoch irreführend. Das Problem besteht darin, dass die Zeichen, die wir ausschließen, nicht gültig sind XML ...Sie sind vollkommen gültige Unicode-Codepunkte.Wir entfernen keine „Nicht-UTF-8-Zeichen“.Wir entfernen UTF-8-Zeichen, die möglicherweise nicht in wohlgeformten XML-Dokumenten vorkommen.

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

Als Methode zum Entfernen ungültiger XML-Zeichen empfehle ich Ihnen die Verwendung XmlConvert.IsXmlChar Methode.Es wurde seit .NET Framework 4 hinzugefügt und wird auch in Silverlight angezeigt.Hier ist das kleine Beispiel:

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-Implementierung von diese AntwortLösung von (mit einem anderen Konstruktor – Sie können gerne den verwenden, den Sie in Ihrer Anwendung benötigen):

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

Modernisieren dnewcombes Antwort: Sie könnten einen etwas einfacheren Ansatz wählen

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

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

Mich würde interessieren, wie die Leistung dieser Methoden im Vergleich zu einem Black-List-Ansatz abschneidet Buffer.BlockCopy.

Hier ist dnewcomeAntwort in einem benutzerdefinierten StreamReader.Es umschließt einfach einen echten Stream-Reader und ersetzt die Zeichen, während sie gelesen werden.

Ich habe nur ein paar Methoden implementiert, um Zeit zu sparen.Ich habe dies in Verbindung mit XDocument.Load und einem Dateistream verwendet und nur die Methode Read(char[] buffer, int index, int count) wurde aufgerufen, also hat es so funktioniert.Möglicherweise müssen Sie zusätzliche Methoden implementieren, damit dies für Ihre Anwendung funktioniert.Ich habe diesen Ansatz verwendet, weil er effizienter zu sein scheint als die anderen Antworten.Ich habe auch nur einen der Konstruktoren implementiert. Sie könnten natürlich jeden der StreamReader-Konstruktoren implementieren, die Sie benötigen, da es sich nur um einen Durchgang handelt.

Ich habe mich dafür entschieden, die Zeichen zu ersetzen, anstatt sie zu entfernen, da dies die Lösung erheblich vereinfacht.Auf diese Weise bleibt die Länge des Textes gleich und es ist nicht erforderlich, einen separaten Index zu verwalten.

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-basierter Ansatz

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

}

Sieh mein Blogeintrag für mehr Details

Die oben genannten Lösungen scheinen dazu zu dienen, ungültige Zeichen vor der Konvertierung in XML zu entfernen.

Verwenden Sie diesen Code, um ungültige XML-Zeichen aus einer XML-Zeichenfolge zu entfernen.z.B.&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/

Geänderte Antwort oder Originalantwort von Neolisk oben.
Änderungen:Wird ein \0-Zeichen übergeben, erfolgt die Entfernung und nicht die Ersetzung.Außerdem wurde die Methode XmlConvert.IsXmlChar(char) verwendet

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

Verwenden Sie diese Funktion, um ungültige XML-Zeichen zu entfernen.

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

ich erschuf eine leicht aktualisierte Version von @Neolisks Antwort, was die unterstützt *Async Funktionen und nutzt .Net 4.0 XmlConvert.IsXmlChar Funktion.

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

Sie können Nicht-UTF-Zeichen wie folgt übergeben:

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+";";
    }
}

Versuchen Sie dies für PHP!

$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top