Разбор странно структурированного XML с помощью lxml - PullRequest
3 голосов
/ 25 октября 2011

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

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

 <root>
  <element>
   ...
  </element>
  <element>
   ...
  </element>
 </root>  

В любом случае, каждый файл состоит из нескольких элементов, с несколькими дочерними элементами (очевидно). Меня сбивает с толку то, что к соответствующим значениям нужно обращаться четырьмя различными способами;

1) Текст узла:

<tag>value</tag>

2) Атрибут:

<tag attribute="value"></tag>

3) Значение, скрытое внутри тега (в данном случае «true»):

<tag><boolean.true/></tag>

4) Значения внутри тегов с одинаковыми именами ("tagA"), но с тегами "grandparent" с разными именами ("tag1" и "tag2"), все в одном элементе. «tagA» мне не нужен, вместо этого я буду искать «tag1» и «tag2».

<element>
   <tag1><tagA>value</tagA><tag1>
   <tag2><tagA>value</tagA></tag2>
</element>

На данный момент у меня есть словарь с каждым файлом в качестве ключа. Значения являются словарями с ключами «атрибут», «текст узла», «тег» и «родительский элемент».

* * Пример тысяча двадцать-один: * * 1 022
{'file1.xml' : 'attributes' : {'Person': 'Id', 'Car' : 'Color'},
               'node text': ['Name', 'Address'],
}

Где «Person» и «Car» являются тегами, а «Id» и «Color» являются именами атрибутов.

Это позволяет легко перебирать все элементы и проверять каждый тег, и, если в словаре есть совпадение (если elem.tag в dict ['file1.xml'] ['attribute']), извлечь значение .

Итак, как я уже сказал, код работает, но мне не нравится мое решение. Кроме того, не все элементы имеют все дочерние элементы (например, Person может не владеть автомобилем, тогда этот тег будет вообще отсутствовать), и мне нужно присвоить этим значениям значение «None». Прямо сейчас я получаю все теги, которые должны существовать для каждого элемента в каждом файле, превращаю их в набор, затем проверяю разницу между ними и набором тегов, из которых я фактически извлек значения для этого элемента. Опять же, код довольно уродливый.

Надеюсь, этот беспорядок имеет какой-то смысл.

редактирование:

Я использовал предложение Дж. Ф. Себастьяна о сохранении xpath для каждого значения в словаре с именем поля в качестве ключа и xpath в качестве значения.

Ответы [ 3 ]

3 голосов
/ 26 октября 2011

Вы можете упростить ваш ввод кода, используя xpath выражения относительно вашего элемента вместо сложной структуры данных, например, # 1-4 падежа:

  1. tag /text ()
  2. tag / @ attribute
  3. name (DTBoolean / * [1])
  4. (tag1 | tag2) / * / text ()

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

Я вывожу его в csv, где каждый элемент - одна строка в файле csv.... Я использую defaultdict для хранения элементов и затем сохраняю их в списке, прежде чем выводить их в csv.

Вы можете использовать обычные dict и csv.DictWriter (fieldnames = xpathdict.keys ()):

# for each element
row_dict = dict.fromkeys(xpathdict.keys())
...
# for each key 
row_dict[key] = element.xpath(xpathdict[key]) or None
...
dictwriter.writerow(row_dict)

Где xpathdict - это отображение между именами полей и соответствующими выражениями xpath.Для простоты вы можете хранить функциональные объекты f(element) -> csv field вместо / в дополнение к выражениям xpath.

2 голосов
/ 25 октября 2011

Я не думаю, что # 3 является допустимым XML, потому что с ним нет открывающего тега, и даже если он где-то еще, он не будет правильно вложен в этом примере.Выражение будет интерпретироваться как закрывающий тег из-за символа <.

1 голос
/ 26 октября 2011

Я предполагаю, что вы захотите взять что-то вроде этого:

<root>
  <element>
    <text_attribute>Some Text</text_attribute>
    <attribute var="blah"/>
    <bool_attribute><boolean.true/></bool_attribute>
  </element>
  <element>
    <text_attribute>Some more Text</text_attribute>
    <attribute var="blah again"/>
    <bool_attribute><boolean.false/></bool_attribute>
  </element>
</root>

И получите что-то вроде этого:

[
   { "text_attribute":"Some Text", "attribute":"blah", "bool_attribute":True },
   { "text_attribute":"Some more Text", "attribute":"blah again", "bool_attribute":False }
]

Для этого я бы сделал что-то вроде этого (не проверено):

# Helper function so we can extract a default from an xpath result if empty
def get_first(x, default_value):
  if(len(x)>0) return x[0]
  return default_value

# Parse one element
def process_element( e ):
  retval = {}
  retval['text_attribute'] = get_first(e.xpath("text_attribute/text()"), "default text")
  retval['attribute'] = get_first( e.xpath("attribute/@var"), "default attribute")
  retval['bool_attribute'] = get_first( e.xpath("bool_attribute/boolean.true"), False )
  return retval

# Parse all the elements
elements = []
elements_xml = xml.xpath('/root/element')
for e in elements_xml:
  elements.push( process_element(e) )
...