使用xpath实现document.querySelector样式选择器进行html解析(四):将选择结果封装进行输出 使用xpath实现document.querySelector样式选择器进行html解析(一):将html转成xml 使用xpath实现document.querySelector样式选择器进行html解析(二):扩展一下xpath以便支持正则

使用xpath实现document.querySelector样式选择器进行html解析(一):将html转成xml

使用xpath实现document.querySelector样式选择器进行html解析(二):扩展一下xpath以便支持正则

使用xpath实现document.querySelector样式选择器进行html解析(三):实现样式选择器

使用xpath实现document.querySelector样式选择器进行html解析(四):将选择结果封装进行输出

-----------------------------------------------------------------

恩,其实到目前为止,关于xpath解析html的样式选择器其实已经完工了,而且,应该说比预期的目的还多出了一丢丢的效果

例如:QuerySelector("*[style*='(?<!\w)font-weight\s*:\s*bold(?!\w)']"),可以选中所有style属性中包含粗体定义的节点

例如:QuerySelector("*:contains(abc.*?xyz)"),可以选中所有正文中包含abc和xyz,且abc在xyz之前的节点

注意到没有,我们可以直接使用正则,当然了,第三章我贴的伪类contains处理之前的那个正则不支持圆括号输入,关于指定属性的正则不支持方括号输入,自己魔改一下就可以完整的支持正则了,或者比较麻烦,需要层深计算,但还是可以实现的,可以参考文盲老顾之前关于正则的博文,但是个人感觉没什么必要了

本章为选择器的最后一章,关于结果输出方式的,有自己代码风格的可以不看,毕竟文盲老顾也是半路出家的c#工作者,没有考虑到的,没有学习到的东西都还很多,有更好的结果输出方案的也请告知文盲,让文盲继续进步哦

-----------------------------------------------------------------

为了方便得到结果之后直接可以用.连接属性,用来直接输出结果,所以QuerySelector方法我们修改一下
        public HtmlObjectResult QuerySelector(string selection)
        {
            string xpath = CssParser.ParseCSS(selection);
            try
            {
                return new HtmlObjectResult(_xml.SelectNodes(xpath, XMLExpand.XPathExpand));
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

直接将返回的XmlNodeList封装到一个类里,作为结果实现

    public class HtmlObjectResult
    {
        private List<HtmlObjectNode> _result = new List<HtmlObjectNode>();
        private int _curr = 0;
        public int Count
        {
            get
            {
                return _result == null ? 0 : _result.Count;
            }
        }
        public HtmlObjectNode[] NodeCollection
        {
            get
            {
                return _result.ToArray();
            }
        }
        public HtmlObjectNode Node
        {
            get
            {
                return _result == null ? null : _result[_curr];
            }
        }
        public HtmlObjectResult(XmlNodeList xnl)
        {
            for (int i = 0; i < xnl.Count; i++)
            {
                _result.Add(new HtmlObjectNode(xnl[i]));
            }
        }
        public HtmlObjectResult Next
        {
            get
            {
                _curr += _curr < _result.Count - 1 ? 1 : 0;
                return this;
            }
        }
        public HtmlObjectResult Previous
        {
            get
            {
                _curr -= _curr > 0 ? 1 : 0;
                return this;
            }
        }
        public HtmlObjectResult First
        {
            get
            {
                _curr = 0;
                return this;
            }
        }
    }

对于结果来说,它其实是一个节点集合,所以,提供一个Count,来表示到底有多少节点被选中

一般情况下,我们都是直接使用的第一个节点作为我们的结果,所以定义一个First,如果需要其他结果,可以用Next、Previous来选择不同的结果,恩,反正都是返回这个结果集本身,只是下标定位改变了而已

然后可以返回当前选中的结果作为输出内容,也就是Node属性,Node也是一个封装后的xml节点,稍后再讲

当然,如果不喜欢这些,可以直接输出所有的结果,NodeCollection可以满足你的需要,当然其中的元素也是被封装好的结果节点

再然后是正式输出我们期望的结果值了

    public class HtmlObjectNode
    {
        private XmlNode _node = null;
        public HtmlObjectNode(XmlNode node)
        {
            _node = node;
        }
        public HtmlObjectNode Next
        {
            get
            {
                return _node == null ? null : _node.NextSibling != null ? new HtmlObjectNode(_node.NextSibling) : this;
            }
        }
        public HtmlObjectNode Previous
        {
            get
            {
                return _node == null ? null : _node.PreviousSibling != null ? new HtmlObjectNode(_node.PreviousSibling) : this;
            }
        }
        public HtmlObjectNode Parent
        {
            get
            {
                return _node == null ? null : _node.ParentNode != null ? new HtmlObjectNode(_node.ParentNode) : this;
            }
        }
        public string InnerHtml
        {
            get
            {
                return _node == null ? null : Regex.Replace(_node.InnerXml, @"<!\[CDATA\[|\]\]>", "", RegexOptions.IgnoreCase).Trim();
            }
        }
        public string OuterHtml
        {
            get
            {
                return _node == null ? null : Regex.Replace(_node.OuterXml, @"<!\[CDATA\[|\]\]>", "", RegexOptions.IgnoreCase).Trim();
            }
        }
        public string InnerText
        {
            get
            {
                return _node == null ? null : _node.InnerText;
            }
        }
        public XmlNode Node
        {
            get
            {
                return _node;
            }
        }
    }

作为被选中的节点,有时候我们需要纯文本内容,有时候需要html内容,html内容有时候需要包含节点本身,有时候不包含

所以,我们的结果输出就直接定义成三个,分别是InnerText、InnerHtml、OuterHtml,这个也符合html本身的习惯

由于我在第一章的时候,将html转成xml的时候还追加了不少的CDataSetion节点,这些节点在作为结果输出的时候应该被删除节点声明,所以我在这里用正则删除了一些信息

当然,有时候某些节点定位非常麻烦,可他相邻的部分节点非常好定位,那么我们通常会定位到我们希望选中的节点之前,例如有一个表格,很多行很多列,没有样式啦、ID啦,甚至数据的位置也可能改变,但表格的格式不变,比如每一个td声明数据名称后,下一个紧跟着的td必定是它对应的值的时候,我们就可以直接定位到这个数据名称的位置,例如 QuerySelector("td:contains(姓名)"),然后使用Next向后移动一个节点,再输出就是对应的值了:QuerySelector("td:contains(姓名)").First.Node.Next.InnerText

需要注意的是,Node之前的Next是在结果集中选择下一个对应的结果,Node之后的Next是在Html中对应的元素的下一个元素

好了,HtmlParser部分基本上讲完了,之后文盲老顾会尝试做一些数据提取方面新的尝试,在尽量减少指正的情况下,如何从页面内获取到我们想要的数据,例如自动解析表格之类的

猜你喜欢

转载自blog.csdn.net/superwfei/article/details/80863512