Come si rimuovono i caratteri esadecimali non validi da un'origine dati basata su XML prima di costruire un XmlReader o XPathDocument che utilizza i dati?

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

  •  09-06-2019
  •  | 
  •  

Domanda

Esiste un modo semplice/generale per pulire un'origine dati basata su XML prima di utilizzarla in un XmlReader in modo da poter consumare con garbo dati XML non conformi alle restrizioni sui caratteri esadecimali imposte su XML?

Nota:

  • La soluzione deve gestire le origini dati XML che utilizzano codifiche di caratteri diverse da UTF-8, EGSpecificando la codifica dei caratteri nella dichiarazione del documento XML.Non mulire la codifica del personaggio della fonte mentre si strizza i personaggi esadecimali non validi è stato un punto critico importante.
  • La rimozione di caratteri esadecimali non validi dovrebbe rimuovere solo valori codificati esadecimali, poiché spesso è possibile trovare valori href nei dati che contengono una stringa che corrisponderebbe a una corrispondenza di stringa per un carattere esadecimale.

Sfondo:

Devo utilizzare un'origine dati basata su XML conforme a un formato specifico (si pensi ai feed Atom o RSS), ma desidero essere in grado di utilizzare origini dati che sono state pubblicate che contengono caratteri esadecimali non validi secondo la specifica XML.

In .NET se si dispone di un flusso che rappresenta l'origine dati XML e quindi si tenta di analizzarlo utilizzando un XmlReader e/o XPathDocument, viene generata un'eccezione a causa dell'inclusione di caratteri esadecimali non validi nei dati XML.Il mio attuale tentativo di risolvere questo problema è analizzare lo stream come una stringa e utilizzare un'espressione regolare per rimuovere e/o sostituire i caratteri esadecimali non validi, ma sto cercando una soluzione più performante.

È stato utile?

Soluzione

Esso potrebbe non essere perfetto (enfasi aggiunta poiché alle persone manca questo disclaimer), ma quello che ho fatto in quel caso è sotto.Puoi regolarlo per l'utilizzo con uno streaming.

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

}

Altri suggerimenti

Mi piace il concetto di lista bianca di Eugene.Dovevo fare una cosa simile al poster originale, ma dovevo supportare tutti i caratteri Unicode, non solo fino a 0x00FD.La specifica XML è:

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

In .NET, la rappresentazione interna dei caratteri Unicode è di soli 16 bit, quindi non possiamo "consentire" 0x10000-0x10FFFF esplicitamente.La specifica XML in modo esplicito non consente i punti di codice surrogato che iniziano a 0xD800 non verranno visualizzati.Tuttavia è possibile che se consentiamo questi punti di codice surrogati nella nostra lista bianca, la codifica utf-8 della nostra stringa potrebbe produrre XML valido alla fine purché sia ​​stata prodotta la codifica utf-8 corretta dalle coppie surrogate di caratteri utf-16 nel file stringa .NET.Tuttavia non ho esplorato questo aspetto, quindi ho scelto la scommessa più sicura e non ho consentito i surrogati nella mia lista bianca.

I commenti nella soluzione di Eugene però sono fuorvianti, il problema è che i caratteri che escludiamo non sono validi XML ...sono punti di codice Unicode perfettamente validi.Non rimuoveremo i "caratteri non utf-8".Stiamo rimuovendo i caratteri utf-8 che potrebbero non essere visualizzati nei documenti XML ben formati.

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

Come metodo per rimuovere caratteri XML non validi ti suggerisco di utilizzare XmlConvert.IsXmlChar metodo.È stato aggiunto da .NET Framework 4 ed è presentato anche in Silverlight.Ecco il piccolo campione:

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

Implementazione DRY di questa risposta(usando un costruttore diverso: sentiti libero di usare quello che ti serve nella tua applicazione):

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

Modernizzare dnewcombe's risposta, potresti adottare un approccio leggermente più semplice

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

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

Sarei interessato a sapere come si confrontano le prestazioni di questi metodi e come si confrontano tutti con l'utilizzo di un approccio di lista nera Buffer.BlockCopy.

Qui è dnewcomein uno StreamReader personalizzato.Avvolge semplicemente un vero lettore di flusso e sostituisce i caratteri man mano che vengono letti.

Ho implementato solo alcuni metodi per risparmiare tempo.L'ho usato insieme a XDocument.Load e un flusso di file ed è stato chiamato solo il metodo Read(char[] buffer, int index, int count), quindi ha funzionato in questo modo.Potrebbe essere necessario implementare metodi aggiuntivi per farlo funzionare per la tua applicazione.Ho usato questo approccio perché sembra più efficiente delle altre risposte.Inoltre ho implementato solo uno dei costruttori, potresti ovviamente implementare qualsiasi costruttore StreamReader di cui hai bisogno, poiché è solo un passaggio.

Ho scelto di sostituire i caratteri anziché rimuoverli perché semplifica notevolmente la soluzione.In questo modo la lunghezza del testo rimane la stessa, quindi non è necessario tenere traccia di un indice separato.

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

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

}

Guarda il mio post sul blog per ulteriori dettagli

Le soluzioni di cui sopra sembrano servire a rimuovere i caratteri non validi prima della conversione in XML.

Utilizza questo codice per rimuovere caratteri XML non validi da una stringa XML.per esempio.&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/

Risposta modificata o risposta originale di Neolisco sopra.
I cambiamenti:viene passato il carattere \0, viene eseguita la rimozione anziché la sostituzione.inoltre, ha utilizzato il metodo 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);
        }
    }

Utilizzare questa funzione per rimuovere caratteri xml non validi.

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

ho creato una versione leggermente aggiornata Di La risposta di @Neolisk, che supporta il *Async funziona e utilizza .Net 4.0 XmlConvert.IsXmlChar funzione.

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

Puoi passare caratteri non UTF con quanto segue:

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

Prova questo per PHP!

$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top