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

15 篇文章 3 订阅
8 篇文章 0 订阅

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

使用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(NameTable nt) : base(nt)
        {

        }
        public XpathContext(NameTable nt,XsltArgumentList args) : base(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 baseUri, string nextbaseUri)
        {
            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 object Invoke(XsltContext xls,object[] 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 object 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 object Invoke(XsltContext xls,object[] args,XPathNavigator doc)
        {
            XmlElement xe = doc.UnderlyingObject as XmlElement;
            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的扩展我们也就写好了,使用这个扩展的方式也很简单,直接 xml.SelectNodes("//div",new XpathContext())即可,嗯,我是将这个扔到一个静态类里,这样只需要实例化一次就可以了

补充两个方法,XmlExpand的

        public static XmlNode addNode(XmlNode node, string name, string namespaceURI)
        {
            if (node == null)
            {
                return null;
            }
            XmlNode n = node.OwnerDocument.CreateNode(XmlNodeType.Element, name, namespaceURI);
            node.AppendChild(n);
            return n;
        }
        public static XmlNode addNode(XmlNode node, string name)
        {
            return addNode(node, name, "");
        }
        public static void setAttribute(XmlNode node, string name, string attrib, string namespaceURI)
        {
            if (node.Name == "#text")
            {
                return;
            }
            if (node.Attributes[name] != null)
            {
                node.Attributes.GetNamedItem(name).Value = attrib;
            }
            else
            {
                XmlNode att = node.OwnerDocument.CreateNode(XmlNodeType.Attribute, name, namespaceURI);
                att.Value = attrib;
                node.Attributes.SetNamedItem(att);
            }
        }
        public static void setAttribute(XmlNode node, string name, string attrib)
        {
            setAttribute(node,name,attrib,"");
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文盲老顾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值