使用 Lxml 创建动态 XML 元素

Create a dynamic XML elements with 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 循环使用它。这确实是丢失的拼图,所以一如既往地感谢您的帮助。

此方法使用 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>