2013-07-08 1 views
0

Я смотрел на другие вопросы здесь, в SO о zip и магии *, которые очень помогли мне в понимании того, как это работает. Например:Использование zip и списков для python для преобразования xml в csv

Хотя я до сих пор немного подумать о том, что происходит на самом деле у меня есть лучшее понимание в настоящее время. Итак, я пытаюсь преобразовать XML-документ в csv. Эта последняя ссылка выше очень близка к тому, что я хочу сделать, однако мой исходный xml не имеет самой согласованной структуры, и именно там я нажимаю стену. Вот пример моего источника XML (упрощены для данного примера):

<?xml version="1.0" encoding="utf-8"?> 
<root> 
    <child> 
     <Name>John</Name> 
     <Surname>Doe</Surname> 
     <Phone>123456</Phone> 
     <Phone>654321</Phone> 
     <Fax>111111</Fax> 
    </child> 
    <child> 
     <Name>Tom</Name> 
     <Surname>Cat</Surname> 
     <Phone>98765</Phone> 
     <Phone>56789</Phone> 
     <Phone>00000</Phone> 
    </child> 
</root> 

Как вы можете видеть, что я могу иметь 2 или более одинаковых элементов под <child>. Кроме того, если определенный элемент не имеет значения, он даже не будет существовать (например, на втором <child>, где нет <Fax>).

Это код, который я в настоящее время:

data = etree.parse(open('test.xml')).findall(".//child") 
tags = ('Name', 'Surname', 'Phone', 'Fax') 

for child in data: 
    for a in zip(*[child.findall(x) for x in tags]): 
     print([x.text for x in a]) 

>> Result: 

['John', 'Doe', '123456', '111111'] 

Хотя это дает мне формат, я могу использовать, чтобы написать CSV, то есть две проблемы:

  1. Он пропускает 2-й ребенок потому что у него нет элемента <Fax> (я полагаю). Если я ищу только для элементов, которые существуют в обоих детей, установив tags = ('Name', 'Surname') тогда он у меня есть 2 списка назад (большой!)

  2. Это первый ребенок на самом деле имеет 2 телефонных номера, но только один возвращается

Из того, что я мог проверить, материал начинает исчезать, когда zip * входит в игру ... Как я могу установить значение по умолчанию, чтобы сохранить пустые значения?

обновления: чтобы сделать его более ясным, что я намерен делать, вот ожидаемый выходной формат CSV (с запятой в качестве разделителя, где несколько значений в каждом поле расщепляется запятой):

John;Joe;123456,654321;111111; 
Tom;Cat;98765,56789;00000;; 

Спасибо!

ответ

0

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

from csv import DictWriter 
from StringIO import StringIO 
import xml.etree 
from xml.etree import ElementTree 

xml_str = \ 
''' 
<?xml version="1.0" encoding="utf-8"?> 
<root> 
    <child> 
     <Name>John</Name> 
     <Surname>Doe</Surname> 
     <Phone>123456</Phone> 
     <Phone>654321</Phone> 
     <Fax>111111</Fax> 
    </child> 
    <child> 
     <Name>Tom</Name> 
     <Surname>Cat</Surname> 
     <Phone>98765</Phone> 
     <Phone>56789</Phone> 
     <Phone>00000</Phone> 
    </child> 
</root> 
''' 

root = ElementTree.parse(StringIO(xml_str.strip())) 
entry_list = [] 
for child_tag in root.iterfind("child"): 
    child_tags = child_tag.getchildren() 

    tag_count = {} 
    [tag_count.__setitem__(tag.tag, tag_count.get(tag.tag, 0) + 1) for tag in child_tags] 

    m_count = dict([(key, 0) for (key, val) in filter(lambda (x, y): y > 1, tag_count.items())]) 

    enum = lambda x: ("%s%s" % (x.tag, (" %d" % m_count.setdefault(x.tag, m_count.pop(x.tag) + 1)) if(tag_count[x.tag] > 1) else ""), x.text) 
    tmp_dict = dict([enum(tag) for tag in child_tags]) 

    entry_list.append(tmp_dict) 

field_order = ["Name", "Surname", "Phone 1", "Phone 2", "Phone 3", "Fax"] 
field_check = lambda q: field_order.index(q) if(field_order.count(q)) else sys.maxint 

all_fields = list(reduce(lambda x, y: x | set(y.keys()), entry_list, set([]))) 
all_fields.sort(cmp=lambda x, y: field_check(x) - field_check(y)) 

with open("test.csv", "w") as file_h: 
    writer = DictWriter(file_h, all_fields, restval="", extrasaction="ignore", dialect="excel", lineterminator="\n") 
    writer.writerow(dict(zip(all_fields, all_fields))) 
    writer.writerows(entry_list) 
+0

Wowww ... это прекрасно работает, но мне придется потратить немало времени, пытаясь понять все, что вы только что там сделали! Благодаря! – bergonzzi

0

Вы говорите в отношении своей первой проблемы, что «[i] f Я ищу только элементы, которые существуют у обоих детей ... У меня есть 2 списка назад», подразумевая, что отсутствие вывода для второго ребенка имеет какое-то отношение к взаимодействию между двумя узлами child. Это не так. Аспект поведения zip, который вы, кажется, игнорируете, заключается в том, что zip прекращает обработку своих аргументов после того, как он исчерпан самый короткий.

Рассмотрим вывод следующего упрощения кода:

for child in data: 
    print [child.findall(x) for x in tags] 

Выход будет (опускаю адреса памяти):

[[<Element 'Name'>], [<Element 'Surname'>], [<Element 'Phone'>, <Element 'Phone'>], [<Element 'Fax'>]] 
[[<Element 'Name'>], [<Element 'Surname'>], [<Element 'Phone'>, <Element 'Phone'>, <Element 'Phone'>], []] 

Обратите внимание, что второй список имеет пустой подсписок (потому что у второго ребенка нет узла Fax). Это означает, что когда вы застегиваете эти подсписки вместе, процесс немедленно останавливается и возвращает пустой список; на его первом проходе он уже исчерпал один из подписок. Это почему ваш второй ребенок опускается на выходе; он не имеет ничего общего с элементами, которыми разделяют дети.

Тот же принцип поведения zip объясняет вашу вторую проблему. Обратите внимание, что первый вышеприведенный список выходов состоит из четырех элементов: списка длиной один для трех ваших тегов и списка длины два с двумя элементами телефона. Когда вы застегиваете их вместе, процесс снова останавливается после исчерпания любого из подписок. В этом случае кратчайший подсписок имеет длину один, поэтому результат выводит только один элемент из подсети телефона.

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

for child in data: 
    print [x.text for x in child] 

это будет производить:

['John', 'Doe', '123456', '654321', '111111'] 
['Tom', 'Cat', '98765', '56789', '00000'] 
+0

Привет, Алп, спасибо за ваш ответ. Однако у него не хватает нескольких очков. Я отредактирую свой ответ, чтобы добавить ожидаемый результат, чтобы сделать его более понятным. Моя цель состоит в том, чтобы преобразовать результат в формат csv, чтобы ваш результат не был в порядке в этом контексте. Это то, что я хочу, в конце концов: 'Джон, Джо, 123456,654321, 111111,' ' Том, Кошка, 98765,56789, 00000 ;;' Так что, если у меня есть 2 '' элементы, они нужно объединить вместе в одном поле «csv». Кроме того, порядок важен, поскольку каждое поле csv должно соответствовать соответствующему заголовку. – bergonzzi

Смежные вопросы