Pack d'agilité HTML obtenir tous les éléments par classe

c# html html-agility-pack

Question

Je me lance à l'assaut du pack d'agilité HTML et j'ai du mal à trouver la bonne façon de procéder.

Par exemple:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

Cependant, vous pouvez évidemment ajouter des classes à bien plus que des divs, alors j’ai essayé cela ..

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

Mais cela ne gère pas les cas où vous ajoutez plusieurs classes et où "float" n'est qu'un exemple comme celui-ci.

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

Y at-il un moyen de gérer tout cela? Je veux fondamentalement sélectionner tous les nœuds qui ont une classe = et qui contiennent float.

** La réponse a été documentée sur mon blog avec une explication complète à l' adresse suivante : Pack d'agilité HTML Obtenir tous les éléments par classe

Réponse acceptée

(Mise à jour 2018-03-17)

Le problème:

Le problème, comme vous l'avez remarqué, est que String.Contains n'effectue pas de vérification des limites de mots. Par conséquent, Contains("float") renvoie true pour "foo float bar" (correct) et "unfloating" (qui est Incorrect).

La solution consiste à faire en sorte que "float" (ou le nom de votre classe souhaitée) apparaisse à côté d'une limite de mot aux deux extrémités. Une limite de mot est soit le début (ou la fin) d'une chaîne (ou d'une ligne), des espaces, une certaine ponctuation, etc. Dans la plupart des expressions régulières, il s'agit de \b . Donc, la regex que vous voulez est simplement: \bfloat\b .

L’inconvénient de l’utilisation d’une instance Regex est qu’elles peuvent être lentes si vous n’utilisez pas l’option .Compiled - et leur compilation peut être lente. Donc, vous devriez mettre en cache l'instance regex. Ceci est plus difficile si le nom de classe que vous recherchez pour les modifications au moment de l'exécution.

Vous pouvez également rechercher une chaîne de mots par mots-frontières sans utiliser d'expression rationnelle en l'implémentant en tant que fonction de traitement de chaîne C #, en veillant à ne pas créer de nouvelle chaîne ou autre allocation d'objet (par exemple, pas à l'aide de String.Split ).

Approche 1: Utiliser une expression régulière:

Supposons que vous souhaitiez simplement rechercher des éléments avec un seul nom de classe spécifié au moment de la conception:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Si vous devez choisir un seul nom de classe au moment de l'exécution, vous pouvez créer une expression régulière:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Si vous avez plusieurs noms de classe et que vous voulez faire correspondre tous, vous pouvez créer un tableau de Regex objets et d' assurer qu'ils sont tous matching, ou les combiner en un seul Regex en utilisant lookarounds, mais cela se traduit dans les expressions effroyablement compliquées - Donc, utiliser un Regex[] est probablement mieux:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Approche 2: Utilisation d'une correspondance de chaîne non regex:

L'avantage d'utiliser une méthode C # personnalisée pour faire la correspondance des chaînes au lieu d'une expression rationnelle est une performance supposément plus rapide et une utilisation réduite de la mémoire (bien que Regex puisse être plus rapide dans certaines circonstances - profilez toujours votre code, gamins!).

Cette méthode ci-dessous: CheapClassListContains fournit une fonction de correspondance de chaîne de contrôle de limite de mot rapide qui peut être utilisée de la même manière que regex.IsMatch :

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Approche 3: Utiliser une bibliothèque de sélecteur CSS:

HtmlAgilityPack est quelque peu stagné. Ne prend pas en charge .querySelector et .querySelectorAll , mais il existe des bibliothèques tierces qui étendent HtmlAgilityPack avec: à savoir Fizzler et CssSelectors . Fizzler et CssSelectors implémentent tous deux QuerySelectorAll , vous pouvez donc l'utiliser de la manière suivante:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Avec les classes définies à l'exécution:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

Réponse populaire

Vous pouvez résoudre votre problème en utilisant la fonction 'contient' dans votre requête Xpath, comme ci-dessous:

var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")

Pour réutiliser ceci dans une fonction, procédez comme suit:

var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi