나는 html 민첩성 팩에 찔러 들고 이것에 대해 올바른 방법을 찾는데 어려움을 겪고있다.
예 :
var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));
그러나 분명히 당신은 클래스를 더 많은 div에 추가 할 수 있습니다. 그래서 div를 시도했습니다.
var allLinksWithDivAndClass = _doc.DocumentNode.SelectNodes("//*[@class=\"float\"]");
하지만 그것은 여러 클래스를 추가하는 경우를 처리하지 않으며 "float"은이 중 하나 일뿐입니다.
class="className float anotherclassName"
이 모든 것을 처리 할 수있는 방법이 있습니까? 기본적으로 클래스가있는 모든 노드를 선택하고 float가 포함됩니다.
** 답변은 내 블로그에 전체 설명과 함께 문서화되어 있습니다 : Html 민첩성 팩 클래스로 모든 요소를 가져 오기
(2018-03-17 업데이트)
문제는 String.Contains
가 단어 경계 검사를 수행하지 않는다는 것입니다. 따라서 Contains("float")
는 "foo float bar"(올바른)와 "unfloating"(둘 다)에 대해 true
를 반환 true
부정확 함).
해결책은 양측 의 단어 경계 옆에 "float"(또는 원하는 클래스 이름이 무엇이든)이 나타나는지 확인 하는 것 입니다. 단어 경계는 문자열 (또는 줄)의 시작 (또는 끝), 공백, 특정 문장 부호 등입니다. 대부분의 정규식에서 이것은 \b
입니다. 그래서 당신이 원하는 정규식은 간단합니다 : \bfloat\b
.
Regex
인스턴스를 사용하는 단점은 .Compiled
옵션을 사용하지 않으면 실행 속도가 느려질 수 있으며 컴파일 속도가 느려질 수 있다는 것입니다. 그래서 당신은 regex 인스턴스를 캐시해야합니다. 런타임에 변경하려는 클래스 이름이 있으면이 작업이 더 어려워집니다.
또는 정규 표현식을 사용하지 않고 C # 문자열 처리 함수로 정규식을 사용하지 않고 단어 경계로 단어를 검색 할 수 있습니다. 새 문자열이나 다른 객체 할당 (예 : String.Split
사용하지 않음)이 발생하지 않도록주의하십시오.
디자인 타임에 지정된 단일 class-name을 가진 요소를 찾고자한다고 가정 해보십시오.
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
객체의 배열을 만들어 모두 일치하는지 확인하거나 lookaround를 사용하여 단일 Regex
결합 할 수 있습니다. 그러나이 경우 몹시 복잡한 표현 이됩니다 . 그래서 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", "") )
)
);
}
정규식 대신 문자열 일치를 수행하는 사용자 지정 C # 메서드를 사용하면 성능이 향상되고 메모리 사용량이 줄어 듭니다. (일부 경우 Regex
가 더 빠를 수도 있지만 항상 코드를 먼저 작성합니다.)
이 메소드는 다음과 같습니다 : CheapClassListContains
는 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;
}
HtmlAgilityPack은 다소 정체되어 있지만 .querySelector
및 .querySelectorAll
지원하지 않지만 .querySelector
을 확장하는 타사 라이브러리, 즉 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 );
}
아래와 같이 Xpath 쿼리에서 'contains'함수를 사용하여 문제를 해결할 수 있습니다.
var allElementsWithClassFloat =
_doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")
함수에서이를 재사용하려면 다음과 유사한 작업을 수행하십시오.
string classToFind = "float";
var allElementsWithClassFloat =
_doc.DocumentNode.SelectNodes(string.Format("//*[contains(@class,'{0}')]", classToFind));