使用xpath实现document.querySelector样式选择器进行html解析(一):将html转成
使用xpath实现document.querySelector样式选择器进行html解析(二):扩展一下xpath以便支持正则
使用xpath实现document.querySelector样式选择器进行html解析(三):实现样式选择器
使用xpath实现document.querySelector样式选择器进行html解析(四):将选择结果封装进行输出
-----------------------------------------------------------------
继续我们的工作,在进行下一步之前,先考虑一下,为了支持css选择器,我们需要使用xpath完成哪些东西
标签选择器。。。。这个很简单嘛,//*[name()='tagName'],完全就是标签选择器嘛,根本都不需要再加工了
id选择器。。。。这个好像也很容易,//*[@id='ID'],貌似也挺容易
再看看类选择器。。。。好像有点问题,//*[contains(@class,'className')] 到是能把符合条件的节点选择出来,但是结果貌似比我们预期的要多了?他连 class="classNameA"、class="PickclassName"之类的也给匹配上了?!
Hmmmmmmmmmmm,好吧,在类选择器上,看来是必须扩展一下xpath的方法了,不管是扩展一个正则支持,还是扩展一个其他自定义函数支持,就看个人爱好了,文盲个人是倾向用正则来搞一下,毕竟除了以上三个基本选择器,后边还有属性选择器等着我们实现类似*=啦、^=啦、$=啦,嗯。。。。。为了再htmlParser中不使用正则,结果编写的实现代码中,正则还是不少啊。。。
好了,我们开始去实现一下xpath的扩展吧,这个东西网上搜一搜还是挺多了,基本上就是XsltContext、IXsltContextFunction来对xpath进行扩展
public class XpathContext: XsltContext { private XsltArgumentList _args; public XpathContext() { } public XpathContext(Na ble nt) : (nt) { } public XpathContext(Na ble nt,XsltArgumentList args) : (nt) { _args = args; } public XsltArgumentList ArgList { get { return _args; } } public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] ArgTypes) { XPathExtensionFunction fun = null; switch (prefix) { case "regex": // 这里是前缀名 switch (name) { case "ismatch": // 这里是函数名,下边的委托中,第一个参数是委托调用的函数名?应该可以这么说吧。。。。 fun = new XPathExtensionFunction("RegexIsMatch", 1, 2, new XPathResultType[] { XPathResultType.NodeSet, XPathResultType.String }, XPathResultType.Boolean); break; } break; } return fun; } public override IXsltContextVariable ResolveVariable(string prefix, string name) { XPathExtensionVariable result = new XPathExtensionVariable(name); return result; } public override int CompareDocument(string Uri, string next Uri) { return 0; } public override bool PreserveWhitespace(XPathNavigator node) { return true; } public override bool Whitespace { get { return true; } } } public class XPathExtensionFunction : IXsltContextFunction { private XPathResultType[] _xprts; private XPathResultType _xprt; private string _fn; private int _min; private int _max; public int Minargs { get { return _min; } } public int Maxargs { get { return _max; } } public XPathResultType[] ArgTypes { get { return _xprts; } } public XPathResultType ReturnType { get { return _xprt; } } public XPathExtensionFunction(string fn, int min, int max, XPathResultType[] argTypes, XPathResultType returnType) { _fn = fn; _min = min; _max = max; _xprts = argTypes; _xprt = returnType; } public Invoke(XsltContext xls, [] args,XPathNavigator doc) { switch (_fn) // 根据函数名,进行具体实现。Hmmmmmmm,应该可以叫做函数名吧。^v^ { case "RegexIsMatch": // 具体实现稍后再说 return false; } return null; } } public class XPathExtensionVariable : IXsltContextVariable { private string _fn = string.Empty; public XPathExtensionVariable(string fn) { _fn = fn; } public Evaluate(XsltContext xsl) { XsltArgumentList vars = ((XpathContext)xsl).ArgList; return vars.GetParam(_fn, null); } public bool IsLocal { get { return false; } } public bool IsParam { get { return false; } } public XPathResultType VariableType { get { return XPathResultType.Any; } } }呵呵,别看上边这些代码一大片,其实。。。。都是网上抄的,嗯,真的,文盲同学抄完了之后,都没弄明白各个方法之间传递的都是什么玩意,结果一不小心掉到坑里了,先不要关正则的实现,看看我们的类选择器应该怎么实现
前边已经说了,//*[contains(@class,'className')]不合适,那么用正则来进行选择就好了,//*[regex:ismatch(@class,'(?<!w)className(?!w)')],嗯,这个正则很标准嘛,肯定不会选择出多余的东西。。。。好吧,我说的早了,被打脸了
问题出在什么地方?仔细调试后发现在具体实现的地方,也就是Invoke方法里,我所设置的@class传递进来的是个什么玩意?怎么看都没有发现和class这个属性有关系。。。。
然后再想想,正则除了需要和属性计算之外,还可以和节点的正文计算,或者下一级指定节点的正文进行计算,嗯。。。xpath有这个功能,比如//div[.='标题']、//div[a=链接],好吧,我们先吧选择器调整调整//*[regex:ismatch('@class','(?<!w)className(?!w)')],嗯,这次Invoke传递进来的参数args的所有元素我都可以看懂了,进来了两个字符串,嘿嘿
具体实现正则其实就很简单了。。。
public Invoke(XsltContext xls, [] args,XPathNavigator doc) { Element xe = doc.Underlying as Element; switch (_fn) { case "RegexIsMatch": string att = args[0].ToString(); string reg = args[1].ToString(); // 按属性匹配 if (att.Substring(0, 1) == "@") { if (xe.Attributes.GetNamedItem(att.Substring(1)) == null) { return false; } else { // 考虑到css选择器是区分大小写的,所以这里的正则就不忽视大小写了 return Regex.IsMatch(xe.Attributes.GetNamedItem(att.Substring(1)).Value, reg); } } // 实现其他正则需要实现的匹配 return false; } return null; }哦了,关于xpath的扩展我们也就写好了,使用这个扩展的方式也很简单,直接 .SelectNodes("//div",new XpathContext())即可,嗯,我是将这个扔到一个静态类里,这样只需要实例化一次就可以了
补充两个方法, Expand的
public static Node addNode( Node node, string name, string namespaceURI) { if (node == null) { return null; } Node n = node.OwnerDocument.CreateNode( NodeType.Element, name, namespaceURI); node.AppendChild(n); return n; } public static Node addNode( Node node, string name) { return addNode(node, name, ""); } public static void setAttribute( Node node, string name, string attrib, string namespaceURI) { if (node.Name == "#text") { return; } if (node.Attributes[name] != null) { node.Attributes.GetNamedItem(name).Value = attrib; } else { Node att = node.OwnerDocument.CreateNode( NodeType.Attribute, name, namespaceURI); att.Value = attrib; node.Attributes.SetNamedItem(att); } } public static void setAttribute( Node node, string name, string attrib) { setAttribute(node,name,attrib,""); }继续阅读与本文标签相同的文章
懒加载和预加载
区块链技术开发 谈信任体系的开发潜力
-
几道高级前端面试题解析
2026-06-02栏目: 教程
-
如何基于 Vue 2 写一套 UI 库?
2026-06-02栏目: 教程
-
如何写出漂亮的 React 组件
2026-06-02栏目: 教程
-
如何在Vue项目中使用vw实现移动端适配
2026-06-02栏目: 教程
-
如何做一个听话的 “输入框”
2026-06-02栏目: 教程
