Как сравнить значения тегов XML и объединить их, когда они совпадают? (Python) - PullRequest
0 голосов
/ 13 апреля 2020

У меня есть файл 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" ncolour="0" size="12.482">C</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">A</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">P</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">T</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">O</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">L</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">O</text>
                <text></text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text></text>
            </textline>
        </textbox>
    </page>
</pages>

Фактический файл намного длиннее. Я хочу сравнить размер слов и объединить последовательные слова с одним и тем же размером, сохраняя теги следующим образом:

    <?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"  ncolour="0" size="12.482">C</text>
        <text font="NUMPTY+ImprintMTnum-it"  ncolour="0" size="12.333">API</text>
        <text font="NUMPTY+ImprintMTnum"  ncolour="0" size="12.482">TOLO III</text>
        </textline>
        </textbox>
    </page>
    </pages>

До сих пор можно было сравнивать атрибуты, но я не не знаю, как сохранить тег. Это код для этого:

    words = []
    root = ET.fromstring(xml)
    pages = root.findall('.//page')
    for page in pages:
        previous_key = None
        current_key = None
        texts = page.findall('.//text')
        for txt in texts:
            if previous_key:
                current_key = (txt.attrib.get('font',previous_key[0]),txt.attrib.get('size',previous_key[1]))
            else:
                current_key = (txt.attrib.get('font','empty'),txt.attrib.get('size','empty'))
            if current_key != previous_key:
                words.append([])
            words[-1].append(txt.text)
            previous_key = current_key

    for group in words:
        if group:
            print(''.join(group))

Чего мне не хватает?

Ответы [ 2 ]

1 голос
/ 26 апреля 2020

Ну, мне потребовалось некоторое время, чтобы понять это ... Я не уверен, что это все еще актуально, но, поскольку для меня это стало своего рода принципиальным вопросом, я опубликую его здесь для ссылка на будущее.

Основным требованием вопроса является то, что элементы должны быть сгруппированы, если они соответствуют определенным требованиям и являются последовательными. Концепция заключается в том, что вы выбираете первый элемент, который удовлетворяет условию, и все последующие элементы. Затем выберите последний элемент, который соответствует условию, и все его предшествующие элементы. Элементы между этими двумя являются вашими целями. Для этого, по крайней мере, в 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>
0 голосов
/ 13 апреля 2020

Это должно работать (не самая чистая вещь, которую я когда-либо писал, но она делает свою работу):

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" ncolour="0" size="12.482">C</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">A</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">P</text>
                <text font="NUMPTY+ImprintMTnum-it" ncolour="0" size="12.333">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">T</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">O</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">L</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">O</text>
                <text></text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text font="NUMPTY+ImprintMTnum" ncolour="0" size="12.482">I</text>
                <text></text>
            </textline>
        </textbox>
    </page>
</pages>
'''
import xml.etree.ElementTree as ET

new_txt = ""
root = ET.fromstring(xml)

def doit(tag, attrib_list, text="", last_size=0):
  if 'size' in attrib_list.keys():
    if attrib_list['size'] != last_size:
      if last_size != 0 :
        s = f"</{tag}>\n<{tag}"
      else:
        s = f"\n<{tag}"
      for k,v in attrib_list.items():
        s += f" {k}=\"{v}\" "
      if tag == "text":
        s += f">{text}"

    else:
      s = text
      pass
  else:
    s = f"\n<{tag}"
    for k,v in attrib_list.items():
      s += f" {k}=\"{v}\" "
    s += f">"
  return s

for lvl0 in root.iter('pages'):
  new_txt += "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
  new_txt += doit(lvl0.tag, lvl0.attrib)
  for lvl1 in lvl0:
    new_txt += doit(lvl1.tag, lvl1.attrib)
    for lvl2 in lvl1:
      new_txt += doit(lvl2.tag, lvl2.attrib)
      for lvl3 in lvl2:
        new_txt += doit(lvl3.tag, lvl3.attrib)
        last_size = 0
        for lvl4 in lvl3:
          if len(lvl4.attrib) == 0:
            new_txt += " "
          else:
            new_txt += doit(lvl4.tag, lvl4.attrib, lvl4.text, last_size)
            last_size = lvl4.get('size')
        new_txt += f"</text>\n</{lvl3.tag}>\n"
      new_txt += f"</{lvl2.tag}>\n"
    new_txt += f"</{lvl1.tag}>\n"
  new_txt += f"</{lvl0.tag}>"

print(new_txt)
...