如何在 Python 中使用 xPath 为 XML 使用前面的同级?
How to use preceding sibling for XML with xPath in Python?
我有一个 XML 结构如下:
<?xml version="1.0" encoding="utf-8"?>
<pages>
<page id="1" bbox="0.000,0.000,462.047,680.315" rotate="0">
<textbox id="0" bbox="179.739,592.028,261.007,604.510">
<textline bbox="179.739,592.028,261.007,604.510">
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">C</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="192.745,592.218,199.339,603.578" ncolour="0" size="12.333">A</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="193.745,592.218,199.339,603.578" ncolour="0" size="12.333">P</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.333">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">T</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">O</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">L</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">O</text>
<text></text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text></text>
</textline>
</textbox>
</page>
</pages>
文本标签中的属性bbox有四个值,我需要一个元素的第一个bbox值和它前面的值之差。也就是说,前两个bbox之间的距离为1。在下面的循环中,我需要找到我取的bbox属性值的前一个兄弟,以便计算两者之间的距离。
def wrap(line, idxList):
if len(idxList) == 0:
return # No elements to wrap
# Take the first element from the original location
idx = idxList.pop(0) # Index of the first element
elem = removeByIdx(line, idx) # The indicated element
# Create "newline" element with "elem" inside
nElem = E.newline(elem)
line.insert(idx, nElem) # Put it in place of "elem"
while len(idxList) > 0: # Process the rest of index list
# Value not used, but must be removed
idxList.pop(0)
# Remove the current element from the original location
currElem = removeByIdx(line, idx + 1)
nElem.append(currElem) # Append it to "newline"
for line in root.iter('textline'):
idxList = []
for elem in line:
bbox = elem.attrib.get('bbox')
if bbox is not None:
tbl = bbox.split(',')
distance = float(tbl[2]) - float(tbl[0])
else:
distance = 100 # "Too big" value
if distance > 10:
par = elem.getparent()
idx = par.index(elem)
idxList.append(idx)
else: # "Wrong" element, wrap elements "gathered" so far
wrap(line, idxList)
idxList = []
# Process "good" elements without any "bad" after them, if any
wrap(line, idxList)
#print(etree.tostring(root, encoding='unicode', pretty_print=True))
我试过像这样使用 xPath:
for x in tree.xpath("//text[@bbox<preceding::text[1]/@bbox+11]"):
print(x)
但它returns没什么。我的路径是不是错了,我怎么能把它插入到循环中?
你的代码失败的原因是关于前面兄弟姐妹的轴名称
是 preceding-sibling(不是 preceding)。
但是这里不需要使用XPath表达式,因为有原生的lxml
获取(第一个)前一个兄弟的方法称为 getprevious.
要检查对前一个 text 节点的访问,请尝试以下循环:
for x in tree.xpath('//text'):
bb = x.attrib.get('bbox')
if bb is not None:
bb = bb.split(',')
print('This: ', bb)
xPrev = x.getprevious()
bb = None
if xPrev is not None:
bb = xPrev.attrib.get('bbox')
if bb is not None:
bb = bb.split(',')
if bb is not None:
print(' Previous: ', bb)
else:
print(' No previous bbox')
它为当前 text 元素打印 bbox 并为
直接在兄弟姐妹之前。
编辑
如果需要,也可以直接访问前面的bbox属性
text元素,调用x.xpath('preceding-sibling::text[1]/@bbox').
但是请记住,这个函数 returns 一个 list 找到的节点,如果没有的话
已经找到了,这个列表是空的(不是None)。
因此,在您使用此结果之前,您必须:
- 检查返回列表的长度(应该 > 0),
- 从此列表中检索第一个元素(bbox属性的文本内容,
在这种情况下,此列表应仅包含 1 个元素),
- 将其拆分为
,
(获取片段列表),
- 检查这个结果的第一个元素是否不为空,
- 转换为 float.
之后就可以使用了,例如与当前 bbox.
中的对应值进行比较
Python 使用非常古老的 XPath 1.0 标准。在 XPath 1.0 中,“<”运算符总是将其操作数转换为数字。所以当你写
//text[@bbox < preceding::text[1]/@bbox + 11]
您正在对 @bbox
个值执行数值差分和数值加法。
但是@bbox
不是一个数字,它是由四个数字组成的以逗号分隔的列表:
179.739,592.028,261.007,604.510
将其转换为数字会产生 NaN(非数字),并且 NaN < NaN
returns false。
要对这样的结构化属性值做任何有用的事情,您确实需要 XPath 2.0 或更高版本。
我有一个 XML 结构如下:
<?xml version="1.0" encoding="utf-8"?>
<pages>
<page id="1" bbox="0.000,0.000,462.047,680.315" rotate="0">
<textbox id="0" bbox="179.739,592.028,261.007,604.510">
<textline bbox="179.739,592.028,261.007,604.510">
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">C</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="192.745,592.218,199.339,603.578" ncolour="0" size="12.333">A</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="193.745,592.218,199.339,603.578" ncolour="0" size="12.333">P</text>
<text font="NUMPTY+ImprintMTnum-it" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.333">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">T</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">O</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">L</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">O</text>
<text></text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text font="NUMPTY+ImprintMTnum" bbox="191.745,592.218,199.339,603.578" ncolour="0" size="12.482">I</text>
<text></text>
</textline>
</textbox>
</page>
</pages>
文本标签中的属性bbox有四个值,我需要一个元素的第一个bbox值和它前面的值之差。也就是说,前两个bbox之间的距离为1。在下面的循环中,我需要找到我取的bbox属性值的前一个兄弟,以便计算两者之间的距离。
def wrap(line, idxList):
if len(idxList) == 0:
return # No elements to wrap
# Take the first element from the original location
idx = idxList.pop(0) # Index of the first element
elem = removeByIdx(line, idx) # The indicated element
# Create "newline" element with "elem" inside
nElem = E.newline(elem)
line.insert(idx, nElem) # Put it in place of "elem"
while len(idxList) > 0: # Process the rest of index list
# Value not used, but must be removed
idxList.pop(0)
# Remove the current element from the original location
currElem = removeByIdx(line, idx + 1)
nElem.append(currElem) # Append it to "newline"
for line in root.iter('textline'):
idxList = []
for elem in line:
bbox = elem.attrib.get('bbox')
if bbox is not None:
tbl = bbox.split(',')
distance = float(tbl[2]) - float(tbl[0])
else:
distance = 100 # "Too big" value
if distance > 10:
par = elem.getparent()
idx = par.index(elem)
idxList.append(idx)
else: # "Wrong" element, wrap elements "gathered" so far
wrap(line, idxList)
idxList = []
# Process "good" elements without any "bad" after them, if any
wrap(line, idxList)
#print(etree.tostring(root, encoding='unicode', pretty_print=True))
我试过像这样使用 xPath:
for x in tree.xpath("//text[@bbox<preceding::text[1]/@bbox+11]"):
print(x)
但它returns没什么。我的路径是不是错了,我怎么能把它插入到循环中?
你的代码失败的原因是关于前面兄弟姐妹的轴名称 是 preceding-sibling(不是 preceding)。
但是这里不需要使用XPath表达式,因为有原生的lxml 获取(第一个)前一个兄弟的方法称为 getprevious.
要检查对前一个 text 节点的访问,请尝试以下循环:
for x in tree.xpath('//text'):
bb = x.attrib.get('bbox')
if bb is not None:
bb = bb.split(',')
print('This: ', bb)
xPrev = x.getprevious()
bb = None
if xPrev is not None:
bb = xPrev.attrib.get('bbox')
if bb is not None:
bb = bb.split(',')
if bb is not None:
print(' Previous: ', bb)
else:
print(' No previous bbox')
它为当前 text 元素打印 bbox 并为 直接在兄弟姐妹之前。
编辑
如果需要,也可以直接访问前面的bbox属性 text元素,调用x.xpath('preceding-sibling::text[1]/@bbox').
但是请记住,这个函数 returns 一个 list 找到的节点,如果没有的话 已经找到了,这个列表是空的(不是None)。
因此,在您使用此结果之前,您必须:
- 检查返回列表的长度(应该 > 0),
- 从此列表中检索第一个元素(bbox属性的文本内容, 在这种情况下,此列表应仅包含 1 个元素),
- 将其拆分为
,
(获取片段列表), - 检查这个结果的第一个元素是否不为空,
- 转换为 float.
之后就可以使用了,例如与当前 bbox.
中的对应值进行比较Python 使用非常古老的 XPath 1.0 标准。在 XPath 1.0 中,“<”运算符总是将其操作数转换为数字。所以当你写
//text[@bbox < preceding::text[1]/@bbox + 11]
您正在对 @bbox
个值执行数值差分和数值加法。
但是@bbox
不是一个数字,它是由四个数字组成的以逗号分隔的列表:
179.739,592.028,261.007,604.510
将其转换为数字会产生 NaN(非数字),并且 NaN < NaN
returns false。
要对这样的结构化属性值做任何有用的事情,您确实需要 XPath 2.0 或更高版本。