Python заменяет содержимое XML на Etree

Я хотел бы проанализировать и сравнить 2 файла XML с парсером Python Etree следующим образом:

У меня есть 2 файла XML с множеством данных. Один на английском языке (исходный файл), другой - соответствующий французский перевод (целевой файл). Например.:

исходный файл:

<AB>
  <CD/>
  <EF>

    <GH>
      <id>123</id>
      <IJ>xyz</IJ>
      <KL>DOG</KL>
      <MN>dogs/dog</MN>
      some more tags and info on same level
      <metadata>
        <entry>
           <cl>Translation</cl>
           <cl>English:dog/dogs</cl>
        </entry>
        <entry>
           <string>blabla</string>
           <string>blabla</string>
        </entry>
            some more strings and entries
      </metadata>
    </GH>

  </EF>
  <stuff/>
  <morestuff/>
  <otherstuff/>
  <stuffstuff/>
  <blubb/>
  <bla/>
  <blubbbla>8</blubbla>
</AB>

Целевой файл выглядит точно так же, но в некоторых местах нет текста:

<MN>chiens/chien</MN>
some more tags and info on same level
<metadata>
  <entry>
    <cl>Translation</cl>
    <cl></cl>
  </entry>

Целевой файл на французском языке имеет пустую межъязыковую ссылку, в которую я хотел бы вставить информацию из исходного файла на английском языке всякий раз, когда два макроса имеют одинаковый идентификатор. Я уже написал некоторый код, в котором я заменил имя строкового тега уникальным именем тега, чтобы идентифицировать межъязыковую ссылку. Теперь я хочу сравнить 2 файла, и если 2 макроса имеют одинаковый идентификатор, замените пустую ссылку во французском файле информацией из английского файла. Раньше я пробовал анализатор minidom, но застрял и хотел бы попробовать Etree сейчас. У меня почти нет знаний о программировании, и мне это очень сложно. Вот код, который у меня есть до сих пор:

    macros = ElementTree.parse(english)

    for tag in macros.getchildren('macro'):
        id_ = tag.find('id')
        data = tag.find('cl')
        id_dict[id_.text] = data.text

    macros = ElementTree.parse(french)

    for tag in macros.getchildren('macro'):
        id_ = tag.find('id')
        target = tag.find('cl')
        if target.text.strip() == '':
        target.text = id_dict[id_.text]

    print (ElementTree.tostring(macros))

Я более чем невежественен, и чтение других сообщений по этому поводу смущает меня еще больше. Буду очень признателен, если кто-нибудь просветит меня :-)


person Kaly    schedule 16.07.2012    source источник
comment
Было бы лучше приложить более сложные образцы, чтобы помочь сделать решение правильным.   -  person pepr    schedule 17.07.2012


Ответы (1)


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

import xml.etree.ElementTree as etree

english_tree = etree.parse('en.xml')
french_tree = etree.parse('fr.xml')

# Get the root elements, as they support iteration
# through their children (direct descendants)
english_root = english_tree.getroot()
french_root = french_tree.getroot()

# Iterate through the direct descendants of the root
# elements in both trees in parallel.
for en, fr in zip(english_root, french_root):
   assert en.tag == fr.tag # check for the same structure
   if en.tag == 'id':
       assert en.text == fr.text # check for the same id

   elif en.tag == 'string':
       if fr.text is None:
           fr.text = en.text
           print en.text      # displaying what was replaced

etree.dump(french_tree)

Для более сложных структур файла цикл по прямым потомкам узла можно заменить итерацией по всем элементам дерева. Если структуры файлов точно такие же, будет работать следующий код:

import xml.etree.ElementTree as etree

english_tree = etree.parse('en.xml')
french_tree = etree.parse('fr.xml')

for en, fr in zip(english_tree.iter(), french_tree.iter()):
   assert en.tag == fr.tag        # check if the structure is the same
   if en.tag == 'id':
       assert en.text == fr.text  # identification must be the same
   elif en.tag == 'string':
       if fr.text is None:
           fr.text = en.text
           print en.text          # display the inserted text

# Write the result to the output file.
with open('fr2.xml', 'w') as fout:
    fout.write(etree.tostring(french_tree.getroot()))

Однако это работает только в тех случаях, когда оба файла имеют абсолютно одинаковую структуру. Давайте следовать алгоритму, который будет использоваться, когда задача будет выполняться вручную. Во-первых, нам нужно найти пустой французский перевод. Затем его следует заменить английским переводом элемента GH с той же идентификацией. Подмножество выражений XPath используется в случае поиска элементов:

import xml.etree.ElementTree as etree

def find_translation(tree, id_):
    # Search fot the GH element with the given identification, and return
    # its translation if found. Otherwise None is returned implicitly.
    for gh in tree.iter('GH'):
       id_elem = gh.find('./id')
       if id_ == id_elem.text:
           # The related GH element found.
           # Find metadata entry, extract the translation.
           # Warning! This is simplification for the fixed position 
           # of the Translation entry.
           me = gh.find('./metadata/entry')
           assert len(me) == 2     # metadata/entry has two elements
           cl1 = me[0]
           assert cl1.text == 'Translation'
           cl2 = me[1]

           return cl2.text


# Body of the program. --------------------------------------------------

english_tree = etree.parse('en.xml')
french_tree = etree.parse('fr.xml')

for gh in french_tree.iter('GH'): # iterate through the GH elements only 
   # Get the identification of the GH section
   id_elem = gh.find('./id')      
   id_ = id_elem.text

   # Find and check the metadata entry, extract the French translation.
   # Warning! This is simplification for the fixed position of the Translation 
   # entry.
   me = gh.find('./metadata/entry')
   assert len(me) == 2     # metadata/entry has two elements
   cl1 = me[0]
   assert cl1.text == 'Translation'
   cl2 = me[1]
   fr_translation = cl2.text

   # If the French translation is empty, put there the English translation
   # from the related element.
   if cl2.text is None:
       cl2.text = find_translation(english_tree, id_)


with open('fr2.xml', 'w') as fout:
   fout.write(etree.tostring(french_tree.getroot()).decode('utf-8'))
person pepr    schedule 17.07.2012
comment
Настало время XPath (стандартный xml.etree.ElementTree поддерживает только некоторые его возможности, но для этого случая они достаточно мощные). Попробуйте модифицированный ответ (последняя часть). Исправьте имена входных/выходных файлов. Тогда я предлагаю почистить комментарии здесь, чтобы сделать их более читабельными и полезными для других. - person pepr; 17.07.2012
comment
Правильно .... если запись перевода не исправлена, могу ли я переименовать тег записи вокруг перевода во что-то уникальное и найти его таким образом, или это не рекомендуется (потому что я пробовал это, и это не сработало, но мне интересно если это правильное направление все же?) - person Kaly; 17.07.2012
comment
Переименование тегов, вероятно, не следует делать вообще. Лучше, если у тега/элемента будет свое особое имя. Таким образом, <string> не является хорошим примером. Но я понимаю, что это может быть решение пользователя вставить этот столбец в интерактивном режиме, и базовое программное обеспечение не может угадать, что хотел пользователь. - person pepr; 17.07.2012