使用Lxml创建动态XML元素

时间:2018-06-29 20:00:36

标签: python xml loops nested lxml

我在使用lxml库生成XML方面获得了一些帮助,这非常有用,而且我能够对其进行扩展以解决大多数问题。我正在努力解决一个用例。我尝试了一个建议,但仍在努力

下面我的数据集的简单表示形式

ID,Currency,Notional,Maturity,Type
ID1,,,,2018-06-01,
ID1-L1,EUR,100,,,Bond
ID1-L2,JPY,110,,A
ID1-L2,CNY,115,,B
ID2,,,,2018-06-01,
ID2-L1,EUR,100,,,Stock
ID2-L2,JPY,110,,C
ID2-L2,JPY,110,,D

基本上,我这里有两条记录ID-1和ID2。 ID-L1,ID-L2等是ID1的子元素,并且会有ID-L2的多个实例。我的问题是我需要识别所有ID-L2等事件,并为每个事件创建一个新元素,然后移至下一个记录ID2并重复。因此,实际上我的结果将如下所示。

<tradeRequests>
    <ids>
    <mainid>ID1</mainid>
            <element>
                <maturityDate>2018-06-01</maturityDate>
            </element>
                <cffixed>
                    <element>
                        <id>ID-L1</id>
                        <currency>EUR</currency>
                    </element>
                </cffixed>
                <cffloat>
                    <element>
                        <id>ID1-L2</id>
                        <currency>JPY</currency>
                    </element>
                    <element>
                        <id>ID1-L2</id>
                        <currency>CNY</currency>
                    </element>
                </cffloat>
        </ids>
</tradeRequests>

因此,这是我使用过的一小段代码,但在本例中,我对值进行了硬编码,而不是引用文件中的内容。

import csv
import lxml.etree
from lxml.builder import E

with open('tc.csv', 'r') as fb:
         results = E.tradeRequests(*(
             E.ids(
                 E.mainid('id'),
                 E.element(
                     E.MaturityDate('maturity'),
                     E.cffixed(
                         E.element(
                             E.id('id'),
                             E.currency('currency'),
                            ),
                        ),
                    E.cffloat(
                        E.element(
                            E.id('id'),
                            E.currency('id'),
                            ) #for r in ids2_rows,
                        ),
                     ),
    )for row in csv.DictReader(fb))
 )
print(lxml.etree.tostring(results, pretty_print=True))

我的问题是,我可以独立地找到一种标识ID-L2的行的方法,但是不确定如何获取for循环来使用它。这确实是缺少的拼图,因此一如既往地乐于助人。

1 个答案:

答案 0 :(得分:1)

此方法在生成元素之前使用itertools.groupby按ID对数据进行分组。这样,可以为每个元素添加一个mainid

import itertools
import csv
import lxml.etree
from lxml.builder import E

with open('tc.csv', 'r') as fb:
    cf = csv.DictReader(fb)
    def groupkey(row):
        return row['ID'].split('-')[0] # group by first part of ID

    result_ids = E.ids()
    result = E.tradeRequests(result_ids)

    for main_id, rows in itertools.groupby(cf, key=groupkey):
        rows = list(rows)
        result_ids.extend([
            E.mainid(main_id),
            E.element(E.maturityDate(rows[0]['Type'])),
            E.cffixed(E.element(E.id(rows[1]['ID']), E.currency(rows[1]['Currency']))),
            E.cffloat(*(E.element(E.id(r['ID']), E.currency(r['Currency']))
                for r in rows[2:])),
        ])
print(lxml.etree.tostring(result, pretty_print=True))

与您在问题中提供的csv一起运行时的结果:

<tradeRequests>
  <ids>
    <mainid>ID1</mainid>
    <element>
      <maturityDate>2018-06-01</maturityDate>
    </element>
    <cffixed>
      <element>
        <id>ID1-L1</id>
        <currency>EUR</currency>
      </element>
    </cffixed>
    <cffloat>
      <element>
        <id>ID1-L2</id>
        <currency>JPY</currency>
      </element>
      <element>
        <id>ID1-L2</id>
        <currency>CNY</currency>
      </element>
    </cffloat>
    <mainid>ID2</mainid>
    <element>
      <maturityDate>2018-06-01</maturityDate>
    </element>
    <cffixed>
      <element>
        <id>ID2-L1</id>
        <currency>EUR</currency>
      </element>
    </cffixed>
    <cffloat>
      <element>
        <id>ID2-L2</id>
        <currency>JPY</currency>
      </element>
      <element>
        <id>ID2-L2</id>
        <currency>JPY</currency>
      </element>
    </cffloat>
  </ids>
</tradeRequests>