【问题标题】:How to use preceding sibling for XML with xPath in Python?如何在 Python 中通过 xPath 使用 XML 的前一个兄弟姐妹?
【发布时间】:2020-04-15 08:04:21
【问题描述】:

我的 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)

但它什么也没返回。我的路径是否错误,如何将其插入循环中?

【问题讨论】:

  • Duplicate with : stackoverflow.com/questions/61213788/… 首先,您必须使用 XSL 文件转换您的 XML 以便使用此 XPath 进行查询。否则它将不起作用。xsltransform.net/aBcT67/1 XPath 表达式将选择符合条件的文本节点: bbox - 前面文本节点的 bbox 值不大于 10。如果这确实是您的目标当然。对于您的示例数据,XPath 表达式将不起作用(因为没有节点遵守此条件)。

标签: python xml xpath tags lxml


【解决方案1】:

您的代码失败的原因是与前面的兄弟姐妹有关的轴名称 是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')

但请记住,此函数返回找到的节点的列表,如果没有 已找到,此列表为(不是)。

因此,在您使用此结果之前,您必须:

  • 检查返回列表的长度(应该>0),
  • 从该列表中检索第一个元素(bbox 属性的文本内容, 在这种情况下,此列表应仅包含 1 个元素),
  • , 拆分(获取片段列表),
  • 检查这个结果的第一个元素是否不为空,
  • 转换为浮点数

之后你就可以使用它了,例如与当前 bbox 中的对应值进行比较。

【讨论】:

  • 谢谢,但问题是我的 XML 有没有属性的文本标签。在这种情况下,bbox 结果为 None,我需要在“None”之前有 bbox。我该怎么做?
  • 您编写了 没有属性的文本标签,因此您的 XML 示例可能是错误的。更正您的 XML 示例(显示这些文本标记在哪里)。不然我不知道怎么处理这个案子。
【解决方案2】:

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 &lt; NaN 返回 false。

要对这样的结构化属性值做任何有用的事情,您确实需要 XPath 2.0 或更高版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-06
    • 1970-01-01
    • 2014-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    相关资源
    最近更新 更多