Nice programing

Html Agility Pack은 클래스별로 모든 요소를 ​​가져옵니다.

nicepro 2020. 10. 28. 21:02
반응형

Html Agility Pack은 클래스별로 모든 요소를 ​​가져옵니다.


나는 html 민첩성 팩을 찌르고 있으며 올바른 방법을 찾는 데 어려움을 겪고 있습니다.

예를 들면 :

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

그러나 분명히 div보다 더 많은 클래스를 추가 할 수 있으므로 이것을 시도했습니다 ..

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

그러나 그것은 여러 클래스를 추가하는 경우를 처리하지 못하며 "float"는 이러한 클래스 중 하나 일뿐입니다 ..

class="className float anotherclassName"

이 모든 것을 처리 할 수있는 방법이 있습니까? 기본적으로 클래스가 있고 float를 포함하는 모든 노드를 선택하고 싶습니다.

** 답변은 내 블로그에 전체 설명과 함께 문서화되어 있습니다. Html Agility Pack Get All Elements by Class


(2018 년 3 월 17 일 업데이트 됨)

문제 :

문제 String.Contains는 단어 경계 검사를 수행하지 않으므로 "foo float bar"(올바른) 및 "unfloating"(잘못된) 모두에 대해 Contains("float")반환된다는 것 true입니다.

해결책은 "float"(또는 원하는 클래스 이름이 무엇이든)가 양쪽 끝에 단어 경계와 함께 표시되도록 하는 것 입니다. 단어 경계는 문자열 (또는 줄)의 시작 (또는 끝), 공백, 특정 구두점 등입니다. 대부분의 정규 표현식에서 이것은입니다 \b. 따라서 원하는 정규식은 간단 \bfloat\b합니다..

Regex인스턴스 사용의 단점 .Compiled옵션을 사용하지 않으면 실행 속도가 느리고 컴파일 속도가 느릴 수 있다는 것입니다. 따라서 정규식 인스턴스를 캐시해야합니다. 찾고있는 클래스 이름이 런타임에 변경되면 더 어렵습니다.

또는 정규식을 C # 문자열 처리 함수로 구현하여 정규식을 사용하지 않고 단어 경계별로 문자열을 검색 할 수 있습니다. 새 문자열이나 기타 개체 할당이 발생하지 않도록주의합니다 (예 :를 사용하지 않음 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객체를 그들이 일치하는 모든 것 확인 또는 단일로 결합 Regexlookarounds를 사용하지만,이 결과 엄청나게 복잡한 표현식에서이 - 그래서 사용 a 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 : 정규식이 아닌 문자열 일치 사용 :

사용자 지정 C # 메서드를 사용하여 정규식 대신 문자열 일치를 수행 할 때의 이점은 가상적으로 성능이 더 빠르고 메모리 사용량이 줄어든다 Regex는 것입니다 (일부 상황에서는 더 빠를 수 있습니다. 항상 코드를 먼저 프로파일 링하십시오!)

이 방법은 다음과 같습니다. CheapClassListContains다음과 같은 방식으로 사용할 수있는 빠른 단어 경계 검사 문자열 일치 기능을 제공합니다 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 선택기 라이브러리 사용 :

HtmlAgilityPack is somewhat stagnated doesn't support .querySelector and .querySelectorAll, but there are third-party libraries that extend HtmlAgilityPack with it: namely Fizzler and CssSelectors. Both Fizzler and CssSelectors implement QuerySelectorAll, so you can use it like so:

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

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

With runtime-defined classes:

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

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

    return doc.QuerySelectorAll( selector  );
}

You can solve your issue by using the 'contains' function within your Xpath query, as below:

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

To reuse this in a function do something similar to the following:

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

I used this extension method a lot in my project. Hope it will help one of you guys.

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

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

You can use the following script:

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

참고URL : https://stackoverflow.com/questions/13771083/html-agility-pack-get-all-elements-by-class

반응형