Comment supprimer les caractères hexadécimaux non valides d'une source de données XML avant de construire un XmlReader ou un XPathDocument qui utilise les données ?

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

  •  09-06-2019
  •  | 
  •  

Question

Existe-t-il un moyen simple/général de nettoyer une source de données basée sur XML avant de l'utiliser dans un XmlReader afin que je puisse consommer gracieusement des données XML non conformes aux restrictions de caractères hexadécimaux imposées à XML ?

Note:

  • La solution doit gérer les sources de données XML qui utilisent des encodages de caractères autres que UTF-8, par exempleEn spécifiant le codage des caractères à la déclaration de document XML.Ne pas mangager le codage du personnage de la source tout en dépouillant des personnages hexadécimaux invalides a été un point de collage majeur.
  • La suppression des caractères hexadécimaux non valides ne doit supprimer que les valeurs codées en hexadécimal, car vous pouvez souvent trouver des valeurs href dans les données qui contiennent une chaîne qui correspondrait à une chaîne pour un caractère hexadécimal.

Arrière-plan:

J'ai besoin de consommer une source de données XML conforme à un format spécifique (pensez aux flux Atom ou RSS), mais je souhaite pouvoir consommer des sources de données qui ont été publiées et qui contiennent des caractères hexadécimaux non valides selon la spécification XML.

Dans .NET, si vous disposez d'un Stream qui représente la source de données XML et que vous essayez ensuite de l'analyser à l'aide d'un XmlReader et/ou d'un XPathDocument, une exception est générée en raison de l'inclusion de caractères hexadécimaux non valides dans les données XML.Ma tentative actuelle pour résoudre ce problème consiste à analyser le Stream sous forme de chaîne et à utiliser une expression régulière pour supprimer et/ou remplacer les caractères hexadécimaux non valides, mais je recherche une solution plus performante.

Était-ce utile?

La solution

Il ce n'est peut-être pas parfait (c'est nous qui soulignons car les gens manquent cette clause de non-responsabilité), mais ce que j'ai fait dans ce cas est ci-dessous.Vous pouvez ajuster pour utiliser avec un flux.

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

}

Autres conseils

J'aime le concept de liste blanche d'Eugene.Je devais faire une chose similaire à l'affiche originale, mais je devais prendre en charge tous les caractères Unicode, pas seulement jusqu'à 0x00FD.La spécification XML est :

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

Dans .NET, la représentation interne des caractères Unicode n'est que de 16 bits, nous ne pouvons donc pas « autoriser » explicitement 0x10000-0x10FFFF.La spécification XML explicitement interdit les points de code de substitution commençant à 0xD800 n'apparaissent pas.Cependant, il est possible que si nous autorisions ces points de code de substitution dans notre liste blanche, le codage utf-8 de notre chaîne pourrait finalement produire du XML valide tant qu'un codage utf-8 approprié a été produit à partir des paires de substitution de caractères utf-16 dans le Chaîne .NET.Cependant, je n'ai pas exploré cela, alors j'ai opté pour la solution la plus sûre et je n'ai pas autorisé les substituts dans ma liste blanche.

Les commentaires dans la solution d'Eugène sont cependant trompeurs, le problème est que les caractères que nous excluons ne sont pas valides dans XML ...ce sont des points de code Unicode parfaitement valides.Nous ne supprimons pas les « caractères non-utf-8 ».Nous supprimons les caractères utf-8 qui peuvent ne pas apparaître dans les documents XML bien formés.

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

Pour supprimer les caractères XML invalides, je vous suggère d'utiliser XmlConvert.IsXmlChar méthode.Il a été ajouté depuis .NET Framework 4 et est également présenté dans Silverlight.Voici le petit échantillon :

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

Mise en œuvre à SEC de cette réponseLa solution de (en utilisant un constructeur différent - n'hésitez pas à utiliser celui dont vous avez besoin dans votre application) :

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

Modernisateur de dnewcombe réponse, vous pourriez adopter une approche légèrement plus simple

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

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

Je serais intéressé de savoir comment les performances de ces méthodes se comparent et comment elles se comparent toutes à une approche de liste noire utilisant Buffer.BlockCopy.

Voici nouveau venuLa réponse de dans un StreamReader personnalisé.Il enveloppe simplement un véritable lecteur de flux et remplace les caractères au fur et à mesure de leur lecture.

Je n'ai mis en œuvre que quelques méthodes pour gagner du temps.Je l'ai utilisé conjointement avec XDocument.Load et un flux de fichiers et seule la méthode Read(char[] buffer, int index, int count) a été appelée, donc cela a fonctionné comme ça.Vous devrez peut-être implémenter des méthodes supplémentaires pour que cela fonctionne pour votre application.J'ai utilisé cette approche car elle semble plus efficace que les autres réponses.Je n'ai également implémenté qu'un seul des constructeurs, vous pouvez évidemment implémenter n'importe lequel des constructeurs StreamReader dont vous avez besoin, car il ne s'agit que d'un passage.

J'ai choisi de remplacer les caractères plutôt que de les supprimer car cela simplifie grandement la solution.De cette façon, la longueur du texte reste la même, il n'est donc pas nécessaire de conserver une trace d'un index distinct.

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

Approche basée sur 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, "");

}

Voir mon article de blog pour plus de détails

Les solutions ci-dessus semblent permettre de supprimer les caractères non valides avant la conversion en XML.

Utilisez ce code pour supprimer les caractères XML non valides d'une chaîne XML.par exemple.&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://balajramesh.wordpress.com/2008/05/30/strip-illegal-xml-characters-based-on-w3c-standard/

Réponse modifiée ou réponse originale par Néolisk ci-dessus.
Changements:du caractère \0 est transmis, la suppression est effectuée plutôt qu'un remplacement.également utilisé la méthode 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);
        }
    }

Utilisez cette fonction pour supprimer les caractères XML non valides.

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

J'ai créé une version légèrement mise à jour de Réponse de @Neolisk, qui soutient le *Async fonctionne et utilise le .Net 4.0 XmlConvert.IsXmlChar fonction.

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

Vous pouvez transmettre des caractères non UTF avec les éléments suivants :

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

Essayez ceci pour PHP !

$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top