这段时间没有写博客,一个原因是由于刚毕业没了学校的学习动力反而下降,另外一个方面由于花了很长时间研究编译原理,然而自己却对它没有太多理解,所以也就没有整理自己的知识,现在慢慢稳定下来,会继续像以前一样更新博客
引言 #
为什么要介绍XPath
呢,我一直以为我对XPath
还是比较了解的,但是随着我对XPath
的了解越来越深,我就对它的越来越敬佩,
简单来说,我以前认为XPath
对结构性文档只能是一把“枪”,指哪打哪,没想到它是一个“巡航导弹”,自动追踪目标。
接下来我们就慢慢从XPath
的基础来谈谈其威力
什么是XPath
#
首先我们要知道XPath
是一种语言,你可以理解它是正则、也可以理解它是SQL
,他们的目的都是从数据中找到我们想要的东西。相比于SQL
从数据库中获取数据,XPath
是从一个XML
文件中获取数据。
好的,我们知道XPath
要操作的对象,什么是XML
,它是一种结构式文档,我们也可以把它看做一种树结构。
<root>
<son> I' m son </son>
</root>
如上面就是一个简单的XML
文档,首先从一个父节点点出发,到最后的一个父节点结束,中间可以有很多子节点,也可以有孙节点,但对于每个节点来说,其父亲只能有一个。
这种文档的出现是由于我们编码程序中树结构
出现而出现的一种数据。相比于正则直接操作文本,XPath
要面对的是是一堆有规律的文本,虽然我们也能使用正则来操作XML
文档,但是正则无法捕获这种XML
的关系,而这个也是XPath
最有力的地方。
在这种关系中,我们最常使用也是最核心的就是父子关系,这个关系简单的通过一个/
就能体现,比如现在我们把上面的XML
复杂化给他添加一个儿子
<root>
<son id="1"> I' m son1 </son>
<son id="2"> I'm son2</son>
</root>
为了获取第二个儿子我们简单的使用这个XPath
语句就能获取到
/root/son[@id='1']
PS: 当然在XPath
中我们可以使用//
来代表一个泛指,通过//son[@id='1']
我们可以把儿子找出来而不关心他的父亲,甚至更进一步,我不关心它是谁,只要它的id
为1
就行,我们用node()
函数来替代一个节点,这样只要//node()[@id='1']
就能拿到id
为1的节点了
我们来看看这个XPath
,我们定义了一种关系root
和son
的父子关系,XPath
的威力就是能用很简单的语句来定义一个节点的关系,在这句中,root
和son
都是节点,我们使用/
来约定节点父子关系,使用[]
来定义节点与自己内部节点或者属性的直接的关系(@
是获取属性)
要掌握XPath
必须要明白,XPath
重要的就是“面”和“点”的关系,“面”代表节点,“点”代表属性,对于面来说,它可以包含很多个点,对于点来说,它有可以看做由很多个更小的面组成(微观上)
就以上面的例子,对于root
和son
这两个节点,其中root
是父节点,我们可以用很多个属性来定义它,比如root[count(son, 2)]
(意思是选择有两个son
的root
),其中对于父节点关系的中son
节点来说(有点绕),他又可以用属性来约束比如root[count(son[@id], 2)]
(意思是选择有两个son
的root
,并且每个son
都有id
这个属性)。从这里我们可以看其实节点和属性是可以相互嵌套的。
从上面这个小例子我们可以看到,XPath
的威力就是它可以用来非常详细的约束节点与其他节点或属性的关系,这种关系可以是绝对的,也可以是相对的,一切取决你的取舍,绝对代表严格,相对代表宽松。
PS:当然我们这里的属性
是一种宽泛的理解,在XPath
中节点还包括text
值,我们可以把它看做节点的一种text
属性。
XPath
的其他关系
#
前面我们介绍了XPath
中最重要的一种关系:父子关系。这个也是我们使用XPath
使用的最主要的一种关系,现在基本上网络上的教程都是基于这种关系的,我们这篇博客主要不详细介绍这种关系,你可以在w3cshool上了解更多内容。
我们先用问题来引入其他的关系,我们再把上面的简单XML
进行修改
<root>
<son id="1"> I' m son1 </son>
<target> son1 target</target>
<son id="2"> I'm son2</son>
<target> son2 target </target>
</root>
我们引入两个目标,现在我们想拿到son
(id为1)的旁边target
,假如我们使用父子关系,使用/root/target[1]
(XPath
索引从1开始)也可以获取到,但是这里引入了一个约束,必须是root
下第一个target
节点,假如这个XML
它是随机的,son
和target
是一个集合,但是他们的位置不定,这个时候我们不能仅仅依赖父子关系来确定节点位置。
这里我们引入兄弟
(sibling)这个概率,son
和target
是一队兄弟,我们能通过知道son
的位置从而定位到target
的位置,那个这个XPath
该怎么写呢,首先我们要确定son
的位置
/root/son[@id='1']
接下来我们通过定位的son
来拿到它后面的兄弟(也有前面的兄弟语法)
/root/son[@id='1']/following-sibling::target[1]
在这里/following-sibling
代表它要找到接下来的兄弟,后面::target[1]
是进一步限定我是要拿到兄弟里面第一个target
,我们可以通过这个网站在线测试一下我们的XPath
当然我们可以通过第二个son
来找到它前面的兄弟,对应语法是下面的
/root/son[@id='2']/preceding-sibling::target[1]
在前面我们可以看到这个following-sibling
和preceding-sibling
他们都是一种寻找兄弟关系的,其实假如我们把-sibling
去掉,他们能更加宽泛。
我们把son
和target
包起来,这个在现实中可能更常见
<root>
<group>
<son id="1"> I' m son1 </son>
<target> son1 target</target>
</group>
<group>
<son id="2"> I'm son2</son>
<target> son2 target </target>
</group>
</root>
假如我们还使用上面的语句,我们会发现,我们没法找到语句,这个时候你把兄弟这个约束去掉
/root/group/son[@id='2']/preceding::target[1]
你会惊奇的发现XPath
准确的找到我们的目标,这个令人震惊的是它能实现一种“翻山越岭”的查找。
假如你使用正则或者普通的父子关系,你必须先找到它的group
然后再使用for
循环来遍历所以的group
找到son
….
总结 #
我们使用简单一个前后关系就能轻松实现上百行代码,当我以前不了解这个XPath
的关系约束前,为了寻找这个约束写过几十行代码才能定位,而现在简简单单一行就搞定,我们不得不佩服前人的智慧,我只想说一句“真香~~~”。