【问题标题】:dataframe to hierarchical xml数据框到分层 xml
【发布时间】:2020-01-01 01:47:15
【问题描述】:

将 csv 读取到数据框,然后使用 lxml 库将其转换为 xml

这是我第一次处理 xml,似乎有部分成功。任何帮助将不胜感激。

用于创建数据框的CSV文件:


Parent,Element,Text,Attribute
,TXLife,"
    ",{'Version': '2.25.00'}
TXLife,UserAuthRequest,"
        ",{}
UserAuthRequest,UserLoginName,*****,{}
UserAuthRequest,UserPswd,"
            ",{}
UserPswd,CryptType,None,{}
UserPswd,Pswd,****,{}
TXLife,TXLifeRequest,"
        ",{'PrimaryObjectID': 'Policy_1'}
TXLifeRequest,TransRefGUID,706D67C1-CC4D-11CF-91FB444554540000,{}
TXLifeRequest,TransType,Holding Change,{'tc': '502'}
TXLifeRequest,TransExeDate,2006-11-19,{}
TXLifeRequest,TransExeTime,13:15:33-07:00,{}
TXLifeRequest,ChangeSubType,"
            ",{}
ChangeSubType,ChangeTC,Change Participant,{'tc': '9'}
TXLifeRequest,OLifE,"
            ",{}
OLifE,Holding,"
                ",{'id': 'Policy_1'}
Holding,HoldingTypeCode,Policy,{'tc': '2'}
Holding,Policy,"
                    ",{}
Policy,PolNumber,1234567,{}
Policy,LineOfBusiness,Annuity,{'tc': '2'}
Policy,Annuity,,{}
OLifE,Party,"
                ",{'id': 'Beneficiary_1'}
Party,PartyTypeCode,Organization,{'tc': '2'}
Party,FullName,The Smith Trust,{}
Party,Organization,"
                    ",{}
Organization,OrgForm,Trust,{'tc': '16'}
Organization,DBA,The Smith Trust,{}
OLifE,Relation,"
                ","{'id': 'Relation_1', 'OriginatingObjectID': 'Policy_1', 'RelatedObjectID': 'Beneficiary_1'}"
Relation,OriginatingObjectType,Holding,{'tc': '4'}
Relation,RelatedObjectType,Party,{'tc': '6'}
Relation,RelationRoleCode,Primary Beneficiary,{'tc': '34'}
Relation,BeneficiaryDesignation,Named,{'tc': '1'}

import lxml.etree as etree
import pandas as pd
import json

# Read the csv file
dfc = pd.read_csv('test_data_txlife.csv') .fillna('NA')
# # Remove rows with comments
# dfc = dfc[~dfc['Element'].str.contains("<cyfunction")].fillna('')
dfc['Attribute'] = dfc['Attribute'].apply(lambda x: x.replace("'", '"'))

# Add the root element for xml
root = etree.Element(dfc['Element'][0])
tree = root.getroottree()

for prnt, elem, txt, attr in dfc[['Parent', 'Element', 'Text', 'Attribute']][1:].values:
    # Convert attributes to json (dictionary)
    attrib = json.loads(attr)
    # list(root) = root.getchildren()
    children = [item for item in str(list(root)).split(' ')]
    rootstring = str(root).split(' ')[1]

#     If the parent is root then add the element as child (appaers to work?)
    if prnt == str(root).split(' ')[1]:
        parent = etree.SubElement(root, elem)

    # If the parent is not root but is one of its children then add the elements to the parent
    elif not prnt == rootstring and prnt in children:
        child = etree.SubElement(parent, elem, attrib).text = txt

#     # If the parent is not in root's descendents then add the childern to the parents
    elif not prnt in [str(item).split(' ') for item in root.iterdescendants()]:
        child = etree.SubElement(parent, elem, attrib).text = txt

print(etree.tostring(tree, pretty_print=True).decode())

实际结果:

<TXLife>
  <UserAuthRequest>
    <UserLoginName>*****</UserLoginName>
    <UserPswd>
            </UserPswd>
    <CryptType>None</CryptType>
    <Pswd>xxxxxx</Pswd>
  </UserAuthRequest>
  <TXLifeRequest>
    <TransRefGUID>706D67C1-CC4D-11CF-91FB444554540000</TransRefGUID>
    <TransType tc="502">Holding Change</TransType>
    <TransExeDate>11/19/2006</TransExeDate>
    <TransExeTime>13:15:33-07:00</TransExeTime>
    <ChangeSubType>
            </ChangeSubType>
    <ChangeTC tc="9">Change Participant</ChangeTC>
    <OLifE>
            </OLifE>
    <Holding id="Policy_1">
                </Holding>
    <HoldingTypeCode tc="2">Policy</HoldingTypeCode>
    <Policy>
                    </Policy>
    <PolNumber>1234567</PolNumber>
    <LineOfBusiness tc="2">Annuity</LineOfBusiness>
    <Annuity>NA</Annuity>
    <Party id="Beneficiary_1">
                </Party>
    <PartyTypeCode tc="2">Organization</PartyTypeCode>
    <FullName>The Smith Trust</FullName>
    <Organization>
                    </Organization>
    <OrgForm tc="16">Trust</OrgForm>
    <DBA>The Smith Trust</DBA>
    <Relation OriginatingObjectID="Policy_1" RelatedObjectID="Beneficiary_1" id="Relation_1">
                </Relation>
    <OriginatingObjectType tc="4">Holding</OriginatingObjectType>
    <RelatedObjectType tc="6">Party</RelatedObjectType>
    <RelationRoleCode tc="34">Primary Beneficiary</RelationRoleCode>
    <BeneficiaryDesignation tc="1">Named</BeneficiaryDesignation>
  </TXLifeRequest>
</TXLife>

期望的结果:

<TXLife Version="2.25.00">
    <UserAuthRequest>
        <UserLoginName>*****</UserLoginName>
        <UserPswd>
            <CryptType>None</CryptType>
            <Pswd>****</Pswd>
        </UserPswd>
    </UserAuthRequest>
    <TXLifeRequest PrimaryObjectID="Policy_1">
        <TransRefGUID>706D67C1-CC4D-11CF-91FB444554540000</TransRefGUID>
        <TransType tc="502">Holding Change</TransType>
        <TransExeDate>2006-11-19</TransExeDate>
        <TransExeTime>13:15:33-07:00</TransExeTime>
        <ChangeSubType>
            <ChangeTC tc="9">Change Participant</ChangeTC>
        </ChangeSubType>
        <OLifE>
            <Holding id="Policy_1">
                <HoldingTypeCode tc="2">Policy</HoldingTypeCode>
                <Policy>
                    <PolNumber>1234567</PolNumber>
                    <LineOfBusiness tc="2">Annuity</LineOfBusiness>
                    <Annuity></Annuity>
                </Policy>
            </Holding>
            <Party id="Beneficiary_1">
                <PartyTypeCode tc="2">Organization</PartyTypeCode>
                <FullName>The Smith Trust</FullName>
                <Organization>
                    <OrgForm tc="16">Trust</OrgForm>
                    <DBA>The Smith Trust</DBA>
                </Organization>
            </Party>
            <Relation id="Relation_1" OriginatingObjectID="Policy_1" RelatedObjectID="Beneficiary_1">
                <OriginatingObjectType tc="4">Holding</OriginatingObjectType>
                <RelatedObjectType tc="6">Party</RelatedObjectType>
                <RelationRoleCode tc="34">Primary Beneficiary</RelationRoleCode>
                <BeneficiaryDesignation tc="1">Named</BeneficiaryDesignation>
            </Relation>
        </OLifE>
    </TXLifeRequest>
</TXLife>

我怎样才能得到如上所示的分层结果?

【问题讨论】:

    标签: python xml pandas lxml


    【解决方案1】:

    你已经有了一个很好的开始!认为最容易一点一点地检查您的代码并解释需要调整的地方,并提出一些改进建议:

    读取和清理数据

    # Read the csv file
    dfc = pd.read_csv('test_data_txlife.csv').fillna('NA')
    # # Remove rows with comments
    # dfc = dfc[~dfc['Element'].str.contains("<cyfunction")].fillna('')
    dfc['Attribute'] = dfc['Attribute'].apply(lambda x: x.replace("'", '"'))
    

    .apply 工作正常,但您还可以使用 .str.replace() 方法,它会更简洁明了(.str 允许您将列的值视为字符串类型并对其进行操作相应地)。

    添加根

    # Add the root element for xml
    root = etree.Element(dfc['Element'][0])
    tree = root.getroottree()
    

    这一切都很好!

    循环遍历行

    for prnt, elem, txt, attr in dfc[['Parent', 'Element', 'Text', 'Attribute']][1:].values:
    

    由于您无论如何都在检索所有列,因此您无需索引到 dfc 来选择它们,因此您可以将这部分取出:

    for prnt, elem, txt, attr in dfc[1:].values:
    

    这很好用,但是有用于迭代 DataFrame 中的项目的内置方法,我们可以使用 itertuples()。这将为每一行返回一个NamedTuple,其中包括索引(基本上是行号)作为元组中的第一项,因此我们需要对此进行调整:

    for idx, prnt, elem, txt, attr in dfc[1:].itertuples():
    

    设置变量

        # Convert attributes to json (dictionary)
        attrib = json.loads(attr)
        # list(root) = root.getchildren()
        children = [item for item in str(list(root)).split(' ')]
        rootstring = str(root).split(' ')[1][1:].values:
    

    早先用双引号代替单引号是一个很好的技巧,因此我们可以使用json 将属性转换为字典。 每个Element 都有一个.tag 属性,我们可以使用它来获取名称,这就是我们想要的:

    children = [item.tag for item in root]
    rootstring = root.tag
    

    list(root)root.getchildren() 都会给我们一个root 的子元素列表,但我们也可以像这样使用for ... inroot 循环它们。

    将元素添加到树中

    #     If the parent is root then add the element as child (appaers to work?)
        if prnt == str(root).split(' ')[1]:
            parent = etree.SubElement(root, elem)
    
        # If the parent is not root but is one of its children then add the elements to the parent
        elif not prnt == rootstring and prnt in children:
            child = etree.SubElement(parent, elem, attrib).text = txt
    
    #     # If the parent is not in root's descendents then add the childern to the parents
        elif not prnt in [str(item).split(' ') for item in root.iterdescendants()]:
            child = etree.SubElement(parent, elem, attrib).text = txt
    
    • str(root).split(' ')[1] 正是我们在上面设置的 rootstring,所以我们可以使用它来代替
    • 由于我们已经在第一个if 语句中检查了prnt == rootstring,如果我们已经到达第一个elif,我们知道它不可能相等,所以我们不需要再次检查它李>
    • 当我们创建孩子时,我们同时有两个分配......这会以某种方式成功创建孩子及其文本(!),但这意味着 child 设置为 text 而不是新的 @ 987654352@。最好分两步完成。
    • 当我们寻找父级时,我们正在创建一个列表列表(split() 返回一个列表),所以它不起作用。我们想要项目标签。

    进行所有这些更改会给我们:

    #     If the parent is root then add the element as child (appaers to work?)
        if prnt == rootstring:
            parent = etree.SubElement(root, elem)
    
        # If the parent is not root but is one of its children then add the elements to the parent
        elif prnt in children:
            child = etree.SubElement(parent, elem, attrib)
            child.text = txt
    
    #     # If the parent is not in root's descendents then add the childern to the parents
        elif not prnt in [item.tag for item in root.iterdescendants()]:
            child = etree.SubElement(parent, elem, attrib)
            child.text = txt
    

    但是这里有几个问题。

    第一部分(if 声明)很好。

    在第二部分(第一个elif 语句)中,我们检查新元素的父元素是否是根的子元素之一。如果是,我们将新元素添加为parent 的子元素。 parent 绝对是root 的一个 的孩子,但我们实际上并没有检查它是否是正确的。这只是我们添加到root 的最后一件事。幸运的是,因为我们的 CSV 已按顺序排列了所有元素,所以这是正确的,但最好更明确一点。

    在第三部分(第二个elif)中,最好检查一下prnt 是否已经存在于树的下方。但是目前,如果prnt 不存在,我们只是将元素添加到parent,这不是它的实际父元素!如果prnt 确实存在,我们根本不会添加元素(所以我们需要一个else 子句)。

    解决方案

    谢天谢地,有一个简单的方法:我们可以使用.find() 找到prnt 元素,无论它在树中的什么位置,然后在那里添加新元素。这也使整个事情变得更短!

    for idx, prnt, elem, txt, attr in dfc[1:].itertuples():
        # Convert attributes to json (dictionary)
        attrib = json.loads(attr)
        # Find parent element
        if prnt == root.tag:
            parent = root
        else:
            parent = root.find(".//" + prnt)
        child = etree.SubElement(parent, elem, attrib)
        child.text = txt
    

    root.find(".//" + prnt) 中的 .// 表示它将在树中的任何位置搜索匹配的元素标记(在此处阅读更多信息:https://lxml.de/tutorial.html#elementpath)。


    最终脚本

    import lxml.etree as etree
    import pandas as pd
    import json
    
    # Read the csv file
    dfc = pd.read_csv('test_data_txlife.csv').fillna("NA")
    dfc['Attribute'] = dfc['Attribute'].str.replace("'", '"').apply(lambda s: json.loads(s))
    
    # Add the root element for xml
    root = etree.Element(dfc['Element'][0], dfc['Attribute'][0])
    
    for idx, prnt, elem, txt, attr in dfc[1:].itertuples():
        # Fix text
        text = txt.strip()
        if not text:
            text = None
        # Find parent element
        if prnt == root.tag:
            parent = root
        else:
            parent = root.find(".//" + prnt)
        # Create element
        child = etree.SubElement(parent, elem, attr)
        child.text = text
    
    xml_string = etree.tostring(root, pretty_print=True).decode().replace(">NA<", "><")
    print(xml_string)
    

    我又做了几个改动:

    • 我将属性字典的json.loads 位移动到我们更改引号时,并使用apply 在末尾添加它。我们需要它,以便在我们创建根元素时准备好字典。
    • 让漂亮的打印正常工作存在一些问题,这就是“修复文本”部分的用途(请参阅 this Stack Overflow question 了解我遇到的问题)。
    • 最好有.fillna("")(用空字符串填充),但如果我们这样做,我们最终会得到&lt;/Annuity&gt;而不是&lt;Annuity&gt;&lt;/Annuity&gt;(这是合法的XML - 如果你有一个元素没有文本或子元素,你可以只做结束标签)。但是要让它按照我们的意愿出现,我们需要它有一些“内容”,以便创建开始标签。所以我把它保留为.fillna("NA"),然后在最后,手动替换输出字符串中的那个。

    还需要注意的是,该脚本(至少)对输入数据做出了四个假设:

    • 父元素在其任何子元素之前创建(即它们出现在 CSV 文件中的更靠前的位置)
    • 元素名称是唯一的(或者至少,任何重复的名称都没有任何子元素,因此我们永远不会在 .find() 可能有多个匹配项的情况下进行操作;.find() 总是返回第一个匹配)
    • 您不希望在最终 XML 中保留任何文本值“NA”(当我们从 Annuity 元素中删除虚假的“NA”文本时,它们也会被删除)
    • 不需要保留仅包含空格的文本

    【讨论】:

    • 非常感谢您的帮助。通过这种出色的反应学到了一些新东西。
    • 不客气! ? 我很喜欢和pandasetree 一起工作,我很喜欢写文章,并在我调整位时看到整个事情是如何简化的。我有点担心这对你来说太晚了,因为我的问题在我的浏览器中打开了几天,所以我很高兴它有帮助! ?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-24
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 2012-09-17
    • 2018-02-03
    • 1970-01-01
    相关资源
    最近更新 更多