현재 xpath 쿼리를 통해 특정 콘텐츠를 검색하기 위해 HtmlAgilityPack을 사용하고 있습니다. 이 같은:
var col = doc.DocumentNode.SelectNodes("//*[text()[contains(., 'foo'] or @*....
이제 정규 표현식을 사용하여 모든 HTML 소스 코드 (= 텍스트, 태그 및 속성)에서 특정 내용을 검색하려고합니다. HtmlAgilityPack을 사용하면 어떻게 될 수 있습니까? HtmlAgilityPack은 xpath + regex를 처리 할 수 있습니까? 아니면 정규식과 HtmlAgilityPack을 사용하여 검색하는 가장 좋은 방법은 무엇입니까?
Html Agility Pack은 XPATH 지원을 위해 기본 .NET XPATH 구현을 사용합니다. 다행히 .NET에서 XPATH는 완전히 확장 가능합니다 (BTW : Microsoft가이 뛰어난 기술에 더 이상 투자하지 않는 것은 부끄러운 일입니다 ...).
자, 제가이 html을 가지고 있다고 가정 해 봅시다 :
<div>hello</div>
<div>hallo</div>
다음은 노드를 'h.llo'정규식과 비교하기 때문에 두 노드를 모두 선택하는 샘플 코드입니다.
HtmlNodeNavigator nav = new HtmlNodeNavigator("mypage.htm");
foreach (var node in SelectNodes(nav, "//div[regex-is-match(text(), 'h.llo')]"))
{
Console.WriteLine(node.OuterHtml); // should dump both div elements
}
그것은 내가 "regex-is-match"라는 새로운 XPATH 함수를 정의한 특별한 Xslt / XPath 컨텍스트를 사용하기 때문에 가능합니다. 다음은 SelectNodes 유틸리티 코드입니다.
public static IEnumerable<HtmlNode> SelectNodes(HtmlNodeNavigator navigator, string xpath)
{
if (navigator == null)
throw new ArgumentNullException("navigator");
XPathExpression expr = navigator.Compile(xpath);
expr.SetContext(new HtmlXsltContext());
object eval = navigator.Evaluate(expr);
XPathNodeIterator it = eval as XPathNodeIterator;
if (it != null)
{
while (it.MoveNext())
{
HtmlNodeNavigator n = it.Current as HtmlNodeNavigator;
if (n != null && n.CurrentNode != null)
{
yield return n.CurrentNode;
}
}
}
}
지원 코드는 다음과 같습니다.
public class HtmlXsltContext : XsltContext
{
public HtmlXsltContext()
: base(new NameTable())
{
}
public override int CompareDocument(string baseUri, string nextbaseUri)
{
throw new NotImplementedException();
}
public override bool PreserveWhitespace(XPathNavigator node)
{
throw new NotImplementedException();
}
protected virtual IXsltContextFunction CreateHtmlXsltFunction(string prefix, string name, XPathResultType[] ArgTypes)
{
return HtmlXsltFunction.GetBuiltIn(this, prefix, name, ArgTypes);
}
public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] ArgTypes)
{
return CreateHtmlXsltFunction(prefix, name, ArgTypes);
}
public override IXsltContextVariable ResolveVariable(string prefix, string name)
{
throw new NotImplementedException();
}
public override bool Whitespace
{
get { return true; }
}
}
public abstract class HtmlXsltFunction : IXsltContextFunction
{
protected HtmlXsltFunction(HtmlXsltContext context, string prefix, string name, XPathResultType[] argTypes)
{
Context = context;
Prefix = prefix;
Name = name;
ArgTypes = argTypes;
}
public HtmlXsltContext Context { get; private set; }
public string Prefix { get; private set; }
public string Name { get; private set; }
public XPathResultType[] ArgTypes { get; private set; }
public virtual int Maxargs
{
get { return Minargs; }
}
public virtual int Minargs
{
get { return 1; }
}
public virtual XPathResultType ReturnType
{
get { return XPathResultType.String; }
}
public abstract object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext);
public static IXsltContextFunction GetBuiltIn(HtmlXsltContext context, string prefix, string name, XPathResultType[] argTypes)
{
if (name == "regex-is-match")
return new RegexIsMatch(context, name);
// TODO: create other functions here
return null;
}
public static string ConvertToString(object argument, bool outer, string separator)
{
if (argument == null)
return null;
string s = argument as string;
if (s != null)
return s;
XPathNodeIterator it = argument as XPathNodeIterator;
if (it != null)
{
if (!it.MoveNext())
return null;
StringBuilder sb = new StringBuilder();
do
{
HtmlNodeNavigator n = it.Current as HtmlNodeNavigator;
if (n != null && n.CurrentNode != null)
{
if (sb.Length > 0 && separator != null)
{
sb.Append(separator);
}
sb.Append(outer ? n.CurrentNode.OuterHtml : n.CurrentNode.InnerHtml);
}
}
while (it.MoveNext());
return sb.ToString();
}
IEnumerable enumerable = argument as IEnumerable;
if (enumerable != null)
{
StringBuilder sb = null;
foreach (object arg in enumerable)
{
if (sb == null)
{
sb = new StringBuilder();
}
if (sb.Length > 0 && separator != null)
{
sb.Append(separator);
}
string s2 = ConvertToString(arg, outer, separator);
if (s2 != null)
{
sb.Append(s2);
}
}
return sb != null ? sb.ToString() : null;
}
return string.Format("{0}", argument);
}
public class RegexIsMatch : HtmlXsltFunction
{
public RegexIsMatch(HtmlXsltContext context, string name)
: base(context, null, name, null)
{
}
public override XPathResultType ReturnType { get { return XPathResultType.Boolean; } }
public override int Minargs { get { return 2; } }
public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
{
if (args.Length < 2)
return false;
return Regex.IsMatch(ConvertToString(args[0], false, null), ConvertToString(args[1], false, null));
}
}
}
regex 함수는 끝에 RegexIsMatch라는 클래스에서 구현됩니다. 그것은 매우 복잡하지 않습니다. xpath "물건"을 매우 유용한 문자열로 강요하려고하는 유틸리티 함수 ConvertToString이 있습니다.
물론,이 기술로, 당신은 아주 작은 코드로 필요한 XPATH 함수를 정의 할 수 있습니다 (저는 대문자 / 소문자 변환을하기 위해 항상 이것을 사용합니다 ...).
직접 인용 ,
HTML은 Chomsky Type 2 문법 (문맥 자유 문법) 이고 RegEx는 Chomsky Type 3 문법 (정규 문법) 입니다. Type 2 문법은 Type 3 문법 ( Chomsky 계층 구조 참조)보다 근본적으로 복잡 하므로이 작업을 수행 할 수는 없습니다. 그러나 많은 사람들이 시도 할 것이며, 어떤 사람들은 성공을 주장 할 것이고 다른 사람들은 잘못을 발견하고 당신을 완전히 망칠 것입니다.
HTML 문서의 일부와 함께 정규식을 사용하는 것이 좋습니다. HtmlAgilityPack
을 사용하여 HTML 문서의 태그와 구조에 대한 정규 표현식을 사용하려고 시도하는 것은 잘못되어 궁극적으로 문제에 보편적 인 해결책을 제공 할 수 없습니다.