0

我一直在这里查看关于 zip 和魔法 * 的其他问题,这对我理解它是如何工作的有很大帮助。例如:

尽管我仍然需要考虑一下实际发生的事情,但我现在有了更好的理解。所以我想要实现的是将 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>

如您所见,我可以在<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. 它跳过第二个孩子,因为它没有<Fax>元素(我想)。如果我只通过设置搜索两个孩子中都存在的元素,tags = ('Name', 'Surname')那么我有 2 个列表返回(太棒了!)

  2. 第一个孩子实际上有 2 个电话号码,但只返回一个

从我可以测试的结果来看,当 zip* 发挥作用时,东西开始消失......我怎么能设置一个默认值以便我可以保留空值?

更新:为了更清楚我打算做什么,这是预期的输出格式(带分号分隔符的 CSV,其中每个字段中的多个值用逗号分隔):

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

谢谢!

4

2 回答 2

0

你说,关于你的第一个问题,“[i]如果我只搜索两个孩子中都存在的元素......我有两个列表”,这意味着第二个孩子缺少输出有一些事情要做与两个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解释了你的第二个问题。请注意,上面的第一个输出列表包含四个元素:三个标签的长度为 1 的列表和长度为 2 的两个电话元素的列表。当您将它们压缩在一起时,该过程会在用尽任何子列表后再次停止。在这种情况下,最短的子列表长度为 1,因此结果仅从电话子列表中提取一个元素。

我不确定您希望输出看起来像什么,但如果您只是尝试为每个子节点构建一个包含该节点中每个元素的文本的列表,您可以执行以下操作:

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

这将产生:

['John', 'Doe', '123456', '654321', '111111']
['Tom', 'Cat', '98765', '56789', '00000']
于 2013-07-08T23:00:14.617 回答
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)
于 2013-07-09T13:16:07.930 回答