Html Agility Pack erhält alle Elemente nach Klassen

c# html html-agility-pack

Frage

Ich begehe einen Html Agility Pack und habe Probleme den richtigen Weg zu finden.

Beispielsweise:

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

Natürlich können Sie Klassen zu viel mehr als divs hinzufügen, also habe ich das versucht.

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

Aber das behandelt nicht die Fälle, in denen Sie mehrere Klassen hinzufügen und "float" ist nur eine davon.

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

Gibt es einen Weg, all dies zu bewältigen? Ich möchte im Grunde alle Knoten auswählen, die eine Klasse = und Float enthalten.

** Antwort wurde in meinem Blog mit einer vollständigen Erklärung dokumentiert unter: Html Agility Pack Holen Sie sich alle Elemente nach Klasse

Akzeptierte Antwort

(Aktualisiert 2018-03-17)

Das Problem:

Das Problem besteht darin, dass String.Contains keine Überprüfung der String.Contains durchführt, daher gibt Contains("float") sowohl für "foo float bar" (korrekt) als auch für "unfloating" (dh für Contains("float") true falsch).

Die Lösung besteht darin, sicherzustellen, dass "float" (oder was auch immer Ihr gewünschter Klassenname ist) neben einer Wortgrenze an beiden Enden erscheint. Eine Wortgrenze ist entweder der Anfang (oder das Ende) einer Zeichenkette (oder Zeile), Leerraum, bestimmte Interpunktion usw. In den meisten regulären Ausdrücken ist dies \b . Die gewünschte Regex ist einfach: \bfloat\b .

Ein Nachteil der Verwendung einer Regex Instanz besteht darin, dass sie langsam ausgeführt werden kann, wenn Sie die Option .Compiled nicht verwenden - und die Kompilierung kann langsam sein. Daher sollten Sie die Regex-Instanz zwischenspeichern. Dies ist schwieriger, wenn der Klassenname, den Sie suchen, zur Laufzeit geändert wird.

Alternativ können Sie eine Zeichenfolge nach Wörtern nach Wortgrenzen suchen, ohne eine Regex zu verwenden, indem Sie die Regex als C # -String-Verarbeitungsfunktion String.Split . String.Split dabei darauf, keine neue Zeichenfolge oder andere Objektzuordnung zu verursachen (z. B. keine Verwendung von String.Split ).

Ansatz 1: Verwenden eines regulären Ausdrucks:

Angenommen, Sie möchten nur nach Elementen mit einem einzigen, zur Entwurfszeit angegebenen Klassennamen suchen:

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

Wenn Sie zur Laufzeit einen einzelnen Klassennamen auswählen müssen, können Sie eine Regex erstellen:

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

Wenn Sie mehrere Klassennamen haben und alle abgleichen möchten, können Sie ein Array von Regex Objekten erstellen und sicherstellen, dass alle übereinstimmen, oder sie mithilfe von Lookarounds zu einem einzigen Regex , was jedoch zu erschreckend komplizierten Ausdrücken führt - Mit einem Regex[] ist es wahrscheinlich besser:

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

Ansatz 2: Nicht-Regex-String-Matching verwenden:

Der Vorteil der Verwendung einer benutzerdefinierten C # -Methode für String-Matching anstelle von Regex ist hypothetisch schnellere Leistung und reduzierte Speicherauslastung (obwohl Regex unter Umständen schneller sein kann - profilieren Sie Ihren Code immer zuerst, Kinder!)

Diese Methode unten: CheapClassListContains bietet eine schnelle Wortgrenzen-Überprüfung String-Matching-Funktion, die auf die gleiche Weise wie 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", "") ) );
    }
}

Methode 3: Verwenden einer CSS Selector-Bibliothek:

HtmlAgilityPack ist etwas stagniert, unterstützt nicht .querySelector und .querySelectorAll , aber es gibt Bibliotheken von Drittanbietern, die HtmlAgilityPack damit erweitern: nämlich Fizzler und CssSelectors . Sowohl Fizzler als auch CssSelectors implementieren QuerySelectorAll , so dass Sie es wie QuerySelectorAll können:

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

Mit runtime-definierten Klassen:

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

Beliebte Antwort

Sie können Ihr Problem lösen, indem Sie die Funktion 'contains' in Ihrer Xpath-Abfrage verwenden:

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

Um dies in einer Funktion wiederzuverwenden, gehen Sie wie folgt vor:

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



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum