76 votes

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

Existe-t-il un moyen facile/général de nettoyer une source de données basée sur XML avant de l'utiliser dans un XmlReader afin de pouvoir consommer gracieusement des données XML qui ne sont pas 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 codages de caractères caractères autres que UTF-8, par exemple en spécifiant le codage des caractères à la déclaration du document XML. Ne pas altérer le codage des caractères de la source en supprimant les caractères caractères hexadécimaux invalides a été un point d'achoppement majeur.
  • La suppression des caractères hexadécimaux non valides ne devrait supprimer que les valeurs codées en hexadécimal, car on peut souvent trouver des valeurs refoulées dans des données qui contiennent une chaîne qui correspondrait à un caractère hexadécimal.

Le contexte :

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

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

78voto

Eugene Katz Points 2784

Il peut ne pas être parfait (c'est nous qui soulignons, car les gens manquent cet avertissement), mais ce que j'ai fait dans ce cas est ci-dessous. Vous pouvez ajuster pour l'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();

}

0 votes

Il n'a pas attrapé 0x3c, une de mes applications pense que c'est hex, j'utilise ibex.

1 votes

Essayez la solution de dnewcome ci-dessous.

2 votes

-1 cette réponse est trompeuse car elle supprime des caractères qui sont valides dans XML, qui ne sont pas des caractères de contrôle et qui sont valides en UTF-8.

60voto

dnewcome Points 1420

J'aime le concept de liste blanche d'Eugène. J'avais besoin de faire quelque chose de similaire à l'affiche originale, mais j'avais besoin de supporter tous les caractères Unicode, pas seulement jusqu'à 0x00FD. La spécification XML est la suivante :

Caractère = #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 indique explicitement interdit l'accès à les points de code de substitution commençant à 0xD800 d'apparaître. Cependant, il est possible que si nous autorisions ces points de code de substitution dans notre liste blanche, l'encodage utf-8 de notre chaîne pourrait produire un XML valide au final, à condition qu'un encodage utf-8 correct soit produit à partir des paires de caractères utf-16 de substitution dans la chaîne .NET. Je n'ai pas exploré cette possibilité, j'ai donc 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 valables 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 des 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();
}

0 votes

Il ajoutera & et cela provoque doc = XDocument.Load(@strXMLPath); faire une exception

1 votes

Bonjour, pensez-vous que XmlConvert.IsXmlChar() serait plus précis ? La réponse d'Eugène a changé depuis votre dernier commentaire. merci

31voto

Igor Kustov Points 543

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

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

9voto

Jodrell Points 14205

Modernisation de de dnewcombe vous pouvez 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());
}

J'aimerais savoir comment se comparent les performances de ces méthodes et comment elles se comparent toutes à une approche par liste noire en utilisant Buffer.BlockCopy .

0 votes

J'ai eu un problème avec la méthode Linq qui lançait System.OutOfMemoryException lorsque la chaîne XML sur des fichiers XML plus importants.

0 votes

@BradJ vraisemblablement, la chaîne passée est très longue dans ces cas-là ?

0 votes

@BradJ en fin de compte, une sorte de transformation de flux serait mieux, vous pourriez passer cela directement à XmlReader.Create au lieu de charger le fichier entier dans une chaîne en mémoire.

4voto

mnaoumov Points 459

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 blogpost pour plus de détails

1 votes

C'est ~50x plus lent que la solution de dnewcome sur ma machine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X