2.1 节点集函数:count 与 position


2.1 节点集函数:count 与 position

本节摘要:节点集函数用来探查「选出来的这堆节点」:有几个、第几个、叫什么名字、在哪个命名空间。last()position() 是位置谓词的骨架;count() 给出数字供宿主使用;name() / local-name() / namespace-uri() 在带前缀的文档里才分得出差别。

核心问题

阅读完本节,你应当能够:

  1. 用 count 统计 book 数量,并解释空路径的 count 是 0 不是报错
  2. 在谓词里正确使用 last 与 position
  3. 用 name 与 local-name 区分带前缀的元素名
  4. 知道 2.0 的交集并集函数在 1.0 引擎里不可用

一、先数三本,再取最后一本

对着三本书样本:

count(/bookstore/book) → 3 /bookstore/book[last()]/title → Learning XML /bookstore/book[position()=last()-1]/title → Harry Potter name(/bookstore/book[1]) → book

原文集把 last() 说成「返回上下文节点列表的大小」,所以它能当位置用:当前位置等于列表大小的那个节点就是最后一个。position() 从 1 起算,不是 0。book[0] 永远空,这是从数组语言带来的手误。

count 的参数是节点集。count(/bookstore/book[@category='web']) 在三本样本里是 1,四本样本里是 2。路径空时 count 为 0,表达式仍然合法。宿主语言里不要把 0 当成「XPath 写错了」的唯一信号——写错和「确实没有」都是 0。

二、这组函数各自管哪一块

函数 1.0 典型写法 我怎么用
last book[last()] 取当前步最后一项
position book[position()<=2] 窗口、分页感
count count(book) 给宿主或再比较
name name() 调试标签真名
local-name local-name() 忽略前缀比名字
namespace-uri namespace-uri() 确认绑的是哪份词汇
id id('sku1') 靠 DTD/Schema 的 ID
lang 见 2.4 语言继承

2.0 还提供交集、并集一类集合运算,以及 existsempty。lxml 默认走 1.0,这些名字会报未知函数。需要集合运算时,在宿主语言里对两次查询结果做集合操作更稳。

id() 依赖文档里真正声明了 ID 类型的属性,HTML 的 id 属性在 XML 解析下常常不是 ID 类型,id('x') 会空。HTML 场景用 //*[@id='x']

⚠️ 常见坑:count(//title) 统计的是 title 元素个数,不是书名字符数。字符数是 string-length(title),而且只作用在上下文那个 title 上。
💡 关键直觉:last 是「这支队伍有多长」,position 是「我现在排第几」。两个数对得上,就是队尾。

图:position 只相对于当前队伍

图:position 只相对于当前队伍

三、name 家族与 2.0 的 extras

无前缀时 name()local-name() 都是 book。一旦元素写成 pub:bookname() 可能是 pub:book(前缀随源文档),local-name() 仍是 booknamespace-uri() 才是那个 URI 字符串。比较名字我更倾向 local-name() 加命名空间检查,而不是把前缀写死——前缀是作者随手起的别名。

count(book/author) 在四本书样本里大于 4,因为 Kick Start 有两个 author。用 count 可以发现「一本多作者」,再用 book[count(author)>1] 把该书筛出来。这是节点集函数和谓词配合的典型写法,原文集 2.1 也把 count 当作过滤条件演示过。

不要在 1.0 里写 for $x in ...。那是 2.0 的 FLWOR 亲戚,属于 XQuery 更多。纯 XPath 1.0 能做的集合探查,基本就是本表这几只扳手。

问题:position()=1[1] 一样吗?

对单个谓词,一样。混进 and 时必须写 position()=1,因为 [1 and @category='web'] 里的 1 会被当成数字谓词而不是「并且」。位置要参与逻辑组合,用 position 函数写清楚。

问题:name() 无参和 name(.)

无参等于对上下文节点。谓词里 name()='book'self::book 近似,后者更地道:它走节点测试,不经过字符串。

四、count 当万用表,id 函数别乱用

每写一条新路径,我习惯先 count。count(/bookstore/book) 得 3 或 4,心里有底。count(/bookstore/book[@category='web']) 在三本样本得 1,四本得 2。若你期望 2 却得 0,问题在谓词或命名空间,不在 count 本身。count 为 0 是合法数字,把它当成异常的只有宿主代码。把 count 写进谓词可以筛「多作者的书」:book[count(author)>1] 正中 Kick Start。这是节点集函数和谓词最自然的配合,原文集 2.1 也把 count 当过滤条件用过。

position()=1[1] 在单独谓词里等价。一旦和 and 组合,必须写 position 函数。book[1 and @category='web'] 里的 1 会被当成「位置等于 1 吗?」而不是「真值 1」,和人话不符。逻辑组合里的位置一律写 position。position()>=2 and position()<=3 取窗口,比写两个谓词再在宿主里切切片直观。

name 家族在无前缀文档里看起来多余。一有 pub:book,name 可能带前缀,local-name 稳定为 book,namespace-uri 才是护照。比较元素身份我倾向 local-name 加 URI,而不是把源码前缀写死进路径——前缀是作者随手起的。调试时把三个函数的结果并排打印,比猜「是不是命名空间」快。

id 函数依赖 DTD 或 Schema 把某属性标成 ID 类型。HTML 里随处可见的 id 属性在 XML 解析下常常不是这种类型,id('sku1') 会空。页面上请用 //*[@id='sku1']。XML 里真有 ID 类型时,id 函数可以从任意位置跳到该节点,比双斜杠按属性搜便宜。先确认类型注解存在,再启用这条捷径。

2.0 的 exists、empty、集合交并,1.0 用节点集布尔和宿主集合代替。不要在 lxml 路径里写 exists。能在 Python 里对两次查询结果做集合运算,就不要逼 1.0 引擎假装自己是 2.0。

五、四本书的编号怎么换队伍

所有 book 是队伍甲:位置 1 cooking、2 children、3 web Kick Start、4 web Learning XML。book[1] 是 cooking。book[last()] 是 Learning XML。book[position()<=2] 是前两本。count(book) 是 4。

过滤 web 得到队伍乙:Kick Start 变成位置 1,Learning XML 变成位置 2。book[@category='web'][1] 是 Kick Start,不是 cooking。book[1][@category='web'] 先取 cooking 再问是不是 web,空。last 在队伍乙里指向 Learning XML。这张「换队伍就换编号」的图,是 1.4 谓词顺序的函数版。写分页窗口必须先确定队伍。

book[count(author)>1] 命中 Kick Start。count 当谓词比在宿主里循环数孩子干净。name() 无前缀时是 book,有 pub 前缀时可能是 pub:book,local-name 仍是 book。id 函数对 HTML 的 id 属性常常空,用 at id。position 从 1 起,book[0] 恒空。exists 是 2.0。

练习:对四本夹具分别求 book[1]book[@category='web'][1](//book)[1] 的 title 文本,写出三行。第一行 Everyday Italian,第二行 Kick Start,第三行也是 Everyday Italian——括号让全集先形成再取第一。三行能对上,位置谓词就算过关。再 count 作者大于 1 的书,确认只有一本。

六、编号跟着队伍走不跟着全书走

全部书是甲队,网络类是乙队,两套编号。甲队一号是烹饪,乙队一号是网络类第一本。先过滤再取一号,和先取一号再过滤,结果可以一个有一个空。分页窗口必须先确定队伍。计数空路径得零,合法,不等于写错。把计数写进筛选可找出多作者的书。名字函数无前缀时看不出差别,一有前缀,带前缀的全名和本地名就分家,真匹配看护照号。标识函数依赖真正的标识类型,超文本里随处可见的标识属性常常不是那种类型,按属性相等更老实。位置从一开始,零号恒空。二点零的存在与空集函数,一点零用节点集当真假代替。每条新路径先计数,数字不对先别套字符串函数。练习把三行位置写法的书名对上,位置课才算过关。

把四本书排成甲队,网络类两本排成乙队,分别取一号。甲队一号是烹饪,乙队一号是入门,不是同一本书。先过滤再取一号,和先取一号再过滤,可以一个有一个空。把三行书名写下来对照:全体一号、网络类一号、全集加括号再取一号。第三行若不加括号,绑定可能落在展开后的每一步上,和全集第一本不是一回事。分页窗口必须先确定队伍再取位置。计数空路径得零,合法。把计数写进筛选可找出多作者的书。位置从一开始,零号恒空。名字函数在有前缀时才分家,本地名忽略贴纸,护照号才是真匹配。标识函数依赖真正的标识类型,页面上随处可见的标识常常不是,按属性相等更老实。高版本的存在函数低版本用节点集当真假代替。每条新路径先计数。

原文集把队长说成返回上下文列表的大小,所以它能当位置用:当前位置等于列表大小的那个人就是队尾。计数参数是节点集,网络类在三本样本里是一,四本样本里是二。路径空时计数为零,表达式仍然合法,宿主不要把零当成唯一的写错信号。名字、本地名、护照号三个一起打印,无前缀时前两个相同,有前缀时全名带贴纸、本地名稳定、护照号才是匹配依据。比较名字倾向本地名加护照检查,不要把源码贴纸写死。标识函数在超文本场景经常空,按属性相等更老实。位置参与逻辑组合时必须写位置函数,光写一和某条件并列,一会被当成位置而不是真值。

对四本夹具分别求全体一号、网络类再取一号、全集加括号再取一号,写出三行书名。第一行烹饪,第二行入门,第三行也是烹饪。三行能对上,位置课才算过关。再计数作者大于一的书,确认只有一本。计数空路径得零,合法,不等于写错。编号跟着队伍走,不跟着全书走。分页窗口必须先确定队伍。位置从一开始,零号恒空。位置参与逻辑组合时必须写位置函数,光写一和某条件并列会被当成位置。名字、本地名、护照号三个一起打印,无前缀时前两个相同,有前缀时全名带贴纸、本地名稳定。比较名字倾向本地名加护照,不要把源码贴纸写死。标识函数依赖真正的标识类型,页面标识常常不是,按属性相等更老实。高版本的存在与空集,低版本用节点集当真假代替。每条新路径先计数,数字不对先别套字符串函数。

三行位置对照写进笔记:全体一号、网络类一号、括号全集一号。对不上就回去看队伍有没有换。计数多作者能发现入门那本有两位。空路径计数为零不要当异常。零号恒空。逻辑组合里的位置写位置函数。名字三件套一起打印才能看见贴纸和护照。标识函数在页面经常空。高版本存在函数低版本用节点集当真假。每条新路径先计数。编号相对当前队伍,这是筛选顺序重要的函数版解释。标识类型没有时不要怪函数,改用属性相等。分页必须先确定队伍再取窗口。从零数的语言习惯是手误来源。

换队伍就会换编号,这是位置函数最值钱的一句。先过滤再取一号,和先取一号再过滤,结果可以一个有一个空,把两行书名写下来对照。

标识函数在没有标识类型时空得理所当然,不要改成铺开全体按属性搜却还以为在走捷径。页面场景直接按属性相等,比幻想类型注解更老实。

窗口分页是换队伍之后的编号,不是全书绝对编号。先留网络类再取前两名,入门和进阶都在;先取前两名再留网络类,可能只剩空。

动手验收把三本版和四本版分开数:三本版全体书应是三,网络类应是一;四本版全体应是四,网络类应是二。数字对不上先查样本贴错了哪一版,再查路径有没有多扫推荐节点。位置窗口也按版本分开记:四本版先留网络类再取前两名,应同时留下入门和进阶;混用三本版会让第二本变成空,看起来像位置函数坏了。

本章回顾

  • count 空路径得 0:合法,不等于语法错误
  • position 从 1 起[0] 恒空
  • last 是队长:与 position 相等的是队尾
  • 编号相对当前队伍:先过滤再取第 1,和先取第 1 再过滤不同
  • local-name 忽略前缀:真匹配靠 URI,见第 3 章
  • id 函数依赖 ID 类型:HTML 的 id 属性请用 @id

下一节处理人类可读的字:contains、substring、normalize-space。书名和空白文本会从这里开始听话。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U