如何通过lxml扩展函数模拟XPath 2.0函数?

5 投票
2 回答
3256 浏览
提问于 2025-04-18 16:32

我正在参考lxml的扩展函数文档,想要模仿upper-case这个XPath 2.0中的函数。

import urllib
from lxml import html, etree

ns = etree.FunctionNamespace(None)
ns['upper-case'] = lambda context, s: str.upper(s)

google_page = urllib.request.urlopen('http://www.google.com').read().decode('latin-1')
google_page_tree = html.fromstring(google_page)

# text == ['Google.com']
text = google_page_tree.xpath('//a[@id="fehl"]/text()')

# TypeError: descriptor 'upper' requires a 'str' object but received a 'list'
text = google_page_tree.xpath('//a[upper-case(@id)="FEHL"]/text()')    

看起来这样做不太对,因为我发现upper-case接收到的是一个空列表[]。有什么想法吗?谢谢。

2 个回答

1

你不需要去模仿 xpath 2.0 的功能。其实你可以通过使用 elementpath 来在 lxml 中使用这些功能。

如果你的 Python 版本是 3.6 或更高,可以通过 pip 来安装它。

    pip install elementpath

然后,导入 elementpath 和 lxml 这两个库。

    import elementpath
    from lxml import etree
    root = etree.XML("<book name='sense and sensibility'/>")
    elementpath.select(root, "upper-case(@name)")

这样做会产生:

    'SENSE AND SENSIBILITY'

如果你的 xpath 返回的是节点,那么返回的类型就是一个 lxml.etree._Element 的列表。所以你可以把 elementpath 看作是 lxml 的一个扩展。

参考资料:

elementpath 文档

XQuery 1.0 和 XPath 2.0 的函数与操作符(第二版)

XPath 2.0 的新特性

3

我对你的XPath API不太熟悉,但在XPath 1.0中,@id可以选择一个包含单个属性节点的节点集合,而在XPath 2.0中,它则选择一个包含单个属性节点的序列。我猜str.upper这个方法是需要一个字符串值的,所以你可以试试用//a[upper-case(string(@id)) = ...],而不是//a[upper-case(@id) = ...]。这样,XPath表达式就会返回一个字符串,Python函数就能正确处理这个字符串了。

撰写回答