Ну, мне потребовалось некоторое время, чтобы понять это ... Я не уверен, что это все еще актуально, но, поскольку для меня это стало своего рода принципиальным вопросом, я опубликую его здесь для ссылка на будущее.
Основным требованием вопроса является то, что элементы должны быть сгруппированы, если они соответствуют определенным требованиям и являются последовательными. Концепция заключается в том, что вы выбираете первый элемент, который удовлетворяет условию, и все последующие элементы. Затем выберите последний элемент, который соответствует условию, и все его предшествующие элементы. Элементы между этими двумя являются вашими целями. Для этого, по крайней мере, в xpath вам нужно использовать функцию intersect()
.
Проблема, в свою очередь, заключается в том, что intersect()
является функцией xpath 2.0, а main python Библиотека для xpath (l xml) поддерживает только xpath 1.0. можно эмулировать intersect()
в xpath 1.0 , но это настолько сложно, что заставит вашу голову кружиться, особенно в этом случае.
Так что нам нужно использовать python библиотека, которая поддерживает xpath 2.0. Есть один - elementpath. Я попытался сделать это с помощью elementpath, но там я столкнулся с другой проблемой. В этом случае правильное применение intersect()
требует использования функции xpath count()
. Однако оказалось, что в реализации count()
в elementpath произошла ошибка. Дэвид Брунато, автор любезно обратился к проблеме, и она была исправлена в самой последней версии elementpath!
Итак, после всего сказанного мы можем теперь попытаться решить актуальную проблему:
#I used a simplified version of the xml to streamline things
sizes = """<?xml version="1.0" encoding="utf-8"?>
<pages>
<page>
<box>
<line>
<text size="12.482">C</text>
<text size="12.333">A</text>
<text size="12.333">P</text>
<text size="12.333">I</text>
<text size="12.482">T</text>
<text size="12.482">O</text>
<text size="12.482">L</text>
<text size="12.482">O</text>
<text></text>
<text size="12.482">I</text>
<text size="12.482">I</text>
<text size="12.482">I</text>
<text></text>
</line>
</box>
</page>
</pages>
"""
import elementpath
from lxml import etree
content = sizes.encode('utf-8')
root = etree.XML(content)
godels = elementpath.select(root, '//text[not(./@size = preceding::text/@size)]/@size') #find out how many different 'size' attribute values there are
for godel in godels:
gc_expres = f'count(//text[@size="{godel}"][not(preceding-sibling::text[1][@size="{godel}"])])' # for each size, create an expression to determine the number of starting positions
g_cnt = elementpath.select(root,gc_expres) #activate the function
for i in range(g_cnt):
loc = i+1 # the range() method, being pythonian, counts from 0; xpath counts from 1
top = f'//text[@size="{godel}"][(not(preceding-sibling::text[1][@size="{godel}"]) or count(preceding-sibling::text)=0)][{loc}]/(., following-sibling::text[@size="{godel}"])' #the expression for starting at the top and going down
bot = f'(//text[@size="{godel}"][following-sibling::text[1][not(@size="{godel}")]])[{loc}]/(.,preceding-sibling::text[@size="{godel}"])' #the expression for starting at the bottom and going up
int_expr = f'{top} intersect {bot}' #the intersect expression
combo = elementpath.select(root, int_expr) #the intersect function in action!
newt = ''.join([str((i.text)) for i in combo]) #now that we have the group, create a string of their combined text values
combo[0].text=newt #replace the text of the first group member with new combined string
for i in range(1,len(combo)): #the range skips over this first group member
combo[0].getparent().remove(combo[i]) #remove all other members of the gorup
print(etree.tostring(root).decode())
Выход:
<pages>
<page>
<box>
<line>
<text size="12.482">C</text>
<text size="12.333">API</text>
<text size="12.482">TOLO</text>
<text/>
<text size="12.482">III</text>
<text/>
</line>
</box>
</page>
</pages>