Подтвердить что ты не робот

Html Agility Pack получает все элементы по классам

Я беру удар в hmml agility pack и не могу найти правильный путь для этого.

Например:

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

Однако, очевидно, вы можете добавлять классы намного больше, чем divs, поэтому я пробовал это.

var allLinksWithDivAndClass = _doc.DocumentNode.SelectNodes("//*[@class=\"float\"]");

Но это не относится к случаям, когда вы добавляете несколько классов, а "float" - это только один из них.

class="className float anotherclassName"

Есть ли способ справиться со всем этим? Я в основном хочу выделить все узлы, которые имеют класс = и содержит float.

** Ответ был задокументирован в моем блоге с полным объяснением: Html Agility Pack Получить все элементы по классу

4b9b3361

Ответ 1

(Обновлено 2018-03-17)

Проблема:

Проблема, как вы заметили, заключается в том, что String.Contains не выполняет проверку границ слов, поэтому Contains("float") вернет true для "foo float bar" (правильный) и "разворачивания" (который неправильно).

Решение состоит в том, чтобы гарантировать, что "float" (или любое другое ваше желаемое имя класса) появляется рядом с границей слов на обоих концах. Слово-граница - это начало (или конец) строки (или строки), пробелов, определенных знаков препинания и т.д. В большинстве регулярных выражений это \b. Итак, вы хотите просто регулярное выражение: \bfloat\b.

Недостатком использования экземпляра Regex является то, что они могут работать медленно, если вы не используете параметр .Compiled - и они могут быть медленными для компиляции. Поэтому вы должны кэшировать экземпляр regex. Это сложнее, если имя класса, которое вы ищете для изменений во время выполнения.

Кроме того, вы можете искать строку для слов по границам слов без использования регулярного выражения, реализуя регулярное выражение как функцию строковой обработки С#, стараясь не вызывать новую строку или другое распределение объектов (например, не используя String.Split).

Подход 1: Использование регулярного выражения:

Предположим, вы просто хотите искать элементы с одним указанным именем класса, указанным в дизайне:

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

Если вам нужно выбрать одно имя класса во время выполнения, вы можете создать регулярное выражение:

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    Regex regex = new Regex( "\\b" + Regex.Escape( className ) + "\\b", RegexOptions.Compiled );

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e => e.Name == "div" && regex.IsMatch( e.GetAttributeValue("class", "") ) );
}

Если у вас есть несколько классов-имен, и вы хотите, чтобы соответствовать все из них, вы можете создать массив Regex объектов и убедиться, что они все соответствия, или объединить их в один Regex, используя lookarounds, но это приводит в чудовищно сложных выражений - поэтому использование Regex[], вероятно, лучше:

using System.Linq;

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String[] classNames) {

    Regex[] exprs = new Regex[ classNames.Length ];
    for( Int32 i = 0; i < exprs.Length; i++ ) {
        exprs[i] = new Regex( "\\b" + Regex.Escape( classNames[i] ) + "\\b", RegexOptions.Compiled );
    }

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            exprs.All( r =>
                r.IsMatch( e.GetAttributeValue("class", "") )
            )
        );
}

Подход 2: Использование сопоставления строк без регулярных выражений:

Преимущество использования пользовательского метода С# для выполнения сопоставления строк вместо регулярного выражения является гипотетически более быстрой производительностью и уменьшенным использованием памяти (хотя в некоторых случаях Regex может быть быстрее), всегда сначала просматривайте свой код, дети!)

Этот метод ниже: CheapClassListContains обеспечивает быструю функцию сопоставления строк с проверкой regex.IsMatch которая может использоваться так же, как regex.IsMatch:

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            CheapClassListContains(
                e.GetAttributeValue("class", ""),
                className,
                StringComparison.Ordinal
            )
        );
}

/// <summary>Performs optionally-whitespace-padded string search without new string allocations.</summary>
/// <remarks>A regex might also work, but constructing a new regex every time this method is called would be expensive.</remarks>
private static Boolean CheapClassListContains(String haystack, String needle, StringComparison comparison)
{
    if( String.Equals( haystack, needle, comparison ) ) return true;
    Int32 idx = 0;
    while( idx + needle.Length <= haystack.Length )
    {
        idx = haystack.IndexOf( needle, idx, comparison );
        if( idx == -1 ) return false;

        Int32 end = idx + needle.Length;

        // Needle must be enclosed in whitespace or be at the start/end of string
        Boolean validStart = idx == 0               || Char.IsWhiteSpace( haystack[idx - 1] );
        Boolean validEnd   = end == haystack.Length || Char.IsWhiteSpace( haystack[end] );
        if( validStart && validEnd ) return true;

        idx++;
    }
    return false;
}

Подход 3: Использование библиотеки CSS Selector:

HtmlAgilityPack несколько застопорен, не поддерживает .querySelector и .querySelectorAll, но есть сторонние библиотеки, которые расширяют HtmlAgilityPack с ним: именно Fizzler и CssSelectors. И Fizzler, и CssSelectors реализуют QuerySelectorAll, поэтому вы можете использовать его так:

private static IEnumerable<HtmlNode> GetDivElementsWithFloatClass(HtmlDocument doc) {

    return doc.QuerySelectorAll( "div.float" );
}

С классами, определенными во время выполнения:

private static IEnumerable<HtmlNode> GetDivElementsWithClasses(HtmlDocument doc, IEnumerable<String> classNames) {

    String selector = "div." + String.Join( ".", classNames );

    return doc.QuerySelectorAll( selector  );
}

Ответ 2

Вы можете решить свою проблему, используя функцию "contains" в вашем запросе Xpath, как показано ниже:

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

Чтобы повторно использовать это в функции, сделайте что-то похожее на следующее:

string classToFind = "float";    
var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes(string.Format("//*[contains(@class,'{0}')]", classToFind));

Ответ 3

Я использовал этот метод расширения много в моем проекте. Надеюсь, это поможет одному из вас, ребята.

public static bool HasClass(this HtmlNode node, params string[] classValueArray)
    {
        var classValue = node.GetAttributeValue("class", "");
        var classValues = classValue.Split(' ');
        return classValueArray.All(c => classValues.Contains(c));
    }

Ответ 4

public static List<HtmlNode> GetTagsWithClass(string html,List<string> @class)
    {
        // LoadHtml(html);           
        var result = htmlDocument.DocumentNode.Descendants()
            .Where(x =>x.Attributes.Contains("class") && @class.Contains(x.Attributes["class"].Value)).ToList();          
        return result;
    }      

Ответ 5

Вы можете использовать следующие script:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => 
    d.Attributes.Contains("class") && d.Attributes["class"].Value.Contains("float")
);