使用xpath实现document.querySelector样式选择器进行html解析(一):将html转成
使用xpath实现document.querySelector样式选择器进行html解析(二):扩展一下xpath以便支持正则
使用xpath实现document.querySelector样式选择器进行html解析(三):实现样式选择器
使用xpath实现document.querySelector样式选择器进行html解析(四):将选择结果封装进行输出
-----------------------------------------------------------------
好了,我们继续下一步,准备实现querySelector。。。。。呃。。。。问问同学们,对样式选择器有多大了解,比如 “#main,div .category,div>span.active ~ *”,这个内容都选择了哪些东西?嗯,扔个html片段上来,然后不要着急向后看,先自己看看能得到什么结果,再和文盲老顾的答案对照一下,看看你的基础知识是否掌握的很牢固,嘿嘿
<!doctype html><html lang="en"> <head> < charset="UTF-8"> < name="Generator" content="EditPlus®"> < name="Author" content=""> < name="Keywords" content=""> < name="De ion" content=""> < >Document</ > </head> <body> <div class="header"> <div class="category"> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span class="active">页面顶部分类列表</span> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> </div> </div> <div class="nav category"> <ul id="main"> <span class="active">导航1</span> <span>导航2</span> <span>导航3</span> <span>导航4</span> <span>导航5</span> </ul> </div> </body></html>-----------------------------------------------------------------
<!doctype html><html lang="en"> <head> < charset="UTF-8"> < name="Generator" content="EditPlus®"> < name="Author" content=""> < name="Keywords" content=""> < name="De ion" content=""> < >Document</ > </head> <body> <div class="header"> <div class="category" ="div .category 选中我啦"> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span>页面顶部分类列表</span> <span class="active">页面顶部分类列表</span> <span ="div>span.active ~ * 选中我啦">页面顶部分类列表</span> <span ="div>span.active ~ * 选中我啦">页面顶部分类列表</span> <span ="div>span.active ~ * 选中我啦">页面顶部分类列表</span> <span ="div>span.active ~ * 选中我啦">页面顶部分类列表</span> </div> </div> <div class="nav category"> <ul id="main" ="#main 选中我啦"> <span class="active">导航1</span> <span>导航2</span> <span>导航3</span> <span>导航4</span> <span>导航5</span> </ul> </div> </body></html>#main
div .category
div>span.active ~ *
嗯。。。。然后。。。说不清楚,先说处理方式。。。。以空白符、大于号、加号、波浪线作为分隔符,将样式进行再次拆分。。。。这个分别是继承选择器、子选择器、相邻选择器和通用选择器。。。。这个里边继承选择器和子选择器很容易实现,分别是//*[name()='div']//*[regex:ismatch('@class','(?<!w)category(?!w)')]和//*[name()='div']/*[name()='span'],至于相邻选择器和通用选择器。。。。。稍后再说,这个比较复杂 (╯‵□′)╯︵┻━┻
还有一个需要注意的地方,上边的选择器里,span.active中间没有空格哦,这个也是在解析时需要处理的地方
经过两次切分了,现在的选择器有哪些了呢
#main
div
.category
span.active
*
嗯,span.active也需要拆,拆成两个样式,但他们的关系是与的关系,好在这个选择器可以写到同一个xpath的条件里
然后,还需要实现诸如[att=value]啦,:first-child伪类啦,:nth-of-type伪类啦,当然这个看自己需要,反正文盲是没去实现伪类,呵呵,原因很直接:采集数据提取一般用不到伪类
-----------------------------------------------------------------
首先,建立一个QuerySelector方法
public void QuerySelector(string selection) { _result = new List< Node>(); _count = 0; string xpath = CssParser.ParseCSS(selection); try { NodeList xnl = _ .SelectNodes(xpath, Expand.XPathExpand); if (xnl != null) { _count = xnl.Count; for (int i = 0; i < xnl.Count; i++) { _result.Add(xnl[i]); } } } catch (Exception ex) { throw ex; } }其中的核心代码只有两句,第一句是将样式转成xpath,即CssParser.ParseCSS,第二句是使用带有我们xpath扩展的方式选取节点,即_ .SelectNodes(xpath, Expand.XPathExpand)
为什么使用xpath来实现样式选择呢,因为 是一个序列化的文档,且xpath中具有多种定位方式,比如节点定位、属性定位、轴定位等等,而css选择器定位呢,基本上就只使用了节点定位,属性定位(类选择器和ID选择器都是属性定位),伪类里才会用到轴定位,比如:first-child,那么,用xpath来实现样式选择器就变得可行了
现在继续,把css转xpath的过程按照上边我们分析的过程一步一步实现,首先是按逗号切分
public static string ParseCSS(string selection) { string result = string.Empty; // 切分逗号,每个逗号为一个单独的选择器,多个选择之间为或的关系 string[] csses = selection.Split(new string[] { "," }, StringSplitOptions.None); for (int i = 0; i < csses.Length; i++) { result += (string.IsNullOrEmpty(result) ? "" : "|") + ParseCssLevel(csses[i].Trim()); } return result; }再然后,按照继承(层级)方式切分
private static string ParseCssLevel(string css) { string result = string.Empty; #region // 切分样式,用来分辨选择器类型:继承,子,相邻,通用 // 空格为继承,可跳跃节点 // > 为子,不可跳跃节点 // + 为相邻,为同级节点的下一个兄弟节点 // ~ 为通用,为同级节点的所有后边的兄弟节点 #endregion MatchCollection mc = Regex.Matches(css, @"([~>+s]*)([^s~>+]+)", RegexOptions.IgnoreCase); for (int i = 0; i < mc.Count; i++) { string tp = mc[i].Groups[1].Value.Trim(); string cssparser = ParseCssClass(mc[i].Groups[2].Value.Trim()); //string pre = new Regex(@"(?<=/)([^/]+$)", RegexOptions.IgnoreCase).Match(xpath).Value; switch (tp) { case "": // 继承 CSS1.0 result += (string.IsNullOrEmpty(result) ? "" : "//") + cssparser; break; case "~": // 通用 CSS3.0 // "preceding-sibling::[$pre]" result = Regex.Replace(result, @"(?<!/)[/]+([^/]+)$", (Regex.IsMatch(cssparser, @"[]]$") ? Regex.Replace(cssparser, @"[]]$", " and preceding-sibling::$1]", RegexOptions.IgnoreCase) : cssparser + "[preceding-sibling::$1]"), RegexOptions.IgnoreCase); break; case ">": // 子 CSS2.0 result += (string.IsNullOrEmpty(result) ? "" : "/") + cssparser; break; case "+": // 相邻 CSS2.0 // "count(preceding-sibling::[$pre]/preceding-sibling::*)+1=count(self::node()/preceding-sibling::*)" result = Regex.Replace(result, @"(?<!/)[/]+([^/]+)$", (Regex.IsMatch(cssparser, @"[]]$") ? Regex.Replace(cssparser, @"(?=[]]$)", " and count(preceding-sibling::$1/preceding-sibling::*)+1=count(self::node()/preceding-sibling::*)") : "[count(preceding-sibling::$1/preceding-sibling::*)+1=count(self::node()/preceding-sibling::*)]"), RegexOptions.IgnoreCase); break; default: throw new Exception("未知的选择器类型"); } } return result; }最后,实现样式选择器最终定位
private static string ParseCssClass(string css) { // 伪类除contains外不进行解析,基本用不到 // 对多值进行匹配,推荐使用*=运算,可直接指定为正则表达式 if (css == "*") { return "//*"; } string result = "//*["; // 切分独立样式选择器 MatchCollection mc = Regex.Matches(css, @"([.#])?([w*]+)(([[^[]]+]|[^.#])*)", RegexOptions.IgnoreCase); for (int i = 0; i < mc.Count; i++) { if (i > 0) { result += " and "; } string c = mc[i].Groups[1].Value.Trim(); string n = mc[i].Groups[2].Value; string p = mc[i].Groups[3].Value; if (n != "*") { switch (c) { case "": result += "name()='" + n + "'"; break; case ".": result += "regex:ismatch('@class','(?<!\w)" + n + "(?!\w)')";//contains(@class,'" + n + "') and break; case "#": result += "@id='" + n + "'"; break; } } if (!string.IsNullOrEmpty(p)) { if (n != "*") { result += " and "; } MatchCollection condition = Regex.Matches(p, @"(?<=[)[^[]]+(?=])|(?<=(:)([^:(]+)()[^()]+(?=))", RegexOptions.IgnoreCase); for (int j = 0; j < condition.Count; j++) { if (j > 0) { result += " and "; } if (condition[j].Groups[1].Value == ":") { // 此处实现伪类选择器 string cl = condition[j].Groups[2].Value; switch (cl) { case "contains": result += "regex:ismatch('.','" + condition[j].Value + "')"; break; default: throw new Exception("不支持的伪类选择器"); } } else { Match m = Regex.Match(condition[j].Value, @"([^=!~^$*|]+)(?:([=!~^$*|]+)(['""]?)([^[]]*)3)?", RegexOptions.IgnoreCase); if (m.Success) { string att = m.Groups[1].Value; string opr = m.Groups[2].Value; string val = m.Groups[4].Value; switch (opr) { case "": // 包含指定属性 result += "@" + att; break; case "=": // 指定属性完全等于指定值 result += "@" + att + "='" + val + "'"; break; case "!=": // 指定属性不完全等于指定值 result += "@" + att + "!='" + val + "'"; break; case "^=": // 指定属性以指定字符开头(指定字符后可跟任意字符) result += "regex:ismatch('@" + att + "','^" + val + "')"; break; case "$=": // 指定属性以指定字符结尾(指定字符前可跟任意字符) result += "regex:ismatch('@" + att + "','" + val + "$')"; break; case "*=": // 指定属性中任意位置包含指定的字符串 result += "regex:ismatch('@" + att + "','" + val + "')"; break; case "~=": // 指定属性中任意单一值等于指定值 result += "regex:ismatch('@" + att + "','(?<!\w)" + val + "(?!\w)')"; break; case "|=": // 指定属性中,以指定值开头,后边可跟其他值或- result += "regex:ismatch('@" + att + "','^" + val + "(?=[\s-]|$)')"; break; } } } } } } result += "]"; return result; }Hmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm,真累
那么现在我们再定位元素的时候就非常简单了,直接使用QuerySelector指令即可,因为html文档已经在类里加载进来了
如果有疑问,还请各位多多留言,共同进步
继续阅读与本文标签相同的文章
-
使用vue-router完成简单导航功能
2026-06-02栏目: 教程
-
区块链技术开发 谈信任体系的开发潜力
2026-06-02栏目: 教程
-
使用xpath实现document.querySelector样式选择器进行html解析(二):扩展一下xpath以便支持正则
2026-06-02栏目: 教程
-
懒加载和预加载
2026-06-02栏目: 教程
-
Java并发编程笔记之基础总结(一)
2026-06-02栏目: 教程
