【问题标题】:How to extract multiple grandchildren/children from XML where one child is a specific value?如何从 XML 中提取多个孙子/孩子,其中一个孩子是特定值?
【发布时间】:2021-08-04 14:41:33
【问题描述】:

我正在使用一个 XML 文件来存储我们创建的聊天机器人的所有“版本”。目前我们有 18 个版本,我只关心最新的一个。我正在尝试找到一种方法来提取此“v18”的所有botDialogGroup 元素及其关联的label 元素。 'botDialogGroup' 和 'label' 之间存在一对多的关系。

这是一个 XML 的 sn-p,其中 botDialogGroup 称为“Transfer”,label 称为“Transfer with a question”。并不是说这只是一个版本的 Bot,一共有 18 个。

链接到示例 XML 文件。 https://pastebin.com/aaDfBPUm

还要注意,fullNamebotVersions 的子代。而botDialogGrouplabelbotVersions 的孙子,他们的父母是botDialogs

<Bot>
  <botVersions>
    <fullName>v18</fullName>
    <botDialogs>
        <botDialogGroup>Transfer</botDialogGroup>
        <botSteps>
            <botVariableOperation>
                <askCollectIfSet>false</askCollectIfSet>
                <botMessages>
                    <message>Would you like to chat with an agent?</message>
                </botMessages>
                <botQuickReplyOptions>
                    <literalValue>Yes</literalValue>
                </botQuickReplyOptions>
                <botQuickReplyOptions>
                    <literalValue>No</literalValue>
                </botQuickReplyOptions>
                <botVariableOperands>
                    <disableAutoFill>true</disableAutoFill>
                    <sourceName>YesOrNoChoices</sourceName>
                    <sourceType>MlSlotClass</sourceType>
                    <targetName>Transfer_To_Agent</targetName>
                    <targetType>ConversationVariable</targetType>
                </botVariableOperands>
                <optionalCollect>false</optionalCollect>
                <quickReplyType>Static</quickReplyType>
                <quickReplyWidgetType>Buttons</quickReplyWidgetType>
                <retryMessages>
                    <message>I&apos;m sorry, I didn&apos;t understand that. You have to select an option to proceed.</message>
                </retryMessages>
                <type>Collect</type>
            </botVariableOperation>
            <type>VariableOperation</type>
        </botSteps>
        <botSteps>
            <botStepConditions>
                <leftOperandName>Transfer_To_Agent</leftOperandName>
                <leftOperandType>ConversationVariable</leftOperandType>
                <operatorType>Equals</operatorType>
                <rightOperandValue>No</rightOperandValue>
            </botStepConditions>
            <botSteps>
                <botVariableOperation>
                    <botVariableOperands>
                        <targetName>Transfer_To_Agent</targetName>
                        <targetType>ConversationVariable</targetType>
                    </botVariableOperands>
                    <type>Unset</type>
                </botVariableOperation>
                <type>VariableOperation</type>
            </botSteps>
            <botSteps>
                <botNavigation>
                    <botNavigationLinks>
                        <targetBotDialog>Main_Menu</targetBotDialog>
                    </botNavigationLinks>
                    <type>Redirect</type>
                </botNavigation>
                <type>Navigation</type>
            </botSteps>
            <type>Group</type>
        </botSteps>
        <botSteps>
            <botStepConditions>
                <leftOperandName>Transfer_To_Agent</leftOperandName>
                <leftOperandType>ConversationVariable</leftOperandType>
                <operatorType>Equals</operatorType>
                <rightOperandValue>Yes</rightOperandValue>
            </botStepConditions>
            <botStepConditions>
                <leftOperandName>Online_Product</leftOperandName>
                <leftOperandType>ConversationVariable</leftOperandType>
                <operatorType>NotEquals</operatorType>
                <rightOperandValue>OTP</rightOperandValue>
            </botStepConditions>
            <botStepConditions>
                <leftOperandName>Online_Product</leftOperandName>
                <leftOperandType>ConversationVariable</leftOperandType>
                <operatorType>NotEquals</operatorType>
                <rightOperandValue>TCF</rightOperandValue>
            </botStepConditions>
            <botSteps>
                <botVariableOperation>
                    <botVariableOperands>
                        <targetName>Transfer_To_Agent</targetName>
                        <targetType>ConversationVariable</targetType>
                    </botVariableOperands>
                    <type>Unset</type>
                </botVariableOperation>
                <type>VariableOperation</type>
            </botSteps>
            <botSteps>
                <botNavigation>
                    <botNavigationLinks>
                        <targetBotDialog>Find_Business_Hours</targetBotDialog>
                    </botNavigationLinks>
                    <type>Call</type>
                </botNavigation>
                <type>Navigation</type>
            </botSteps>
            <type>Group</type>
        </botSteps>
        <botSteps>
            <botNavigation>
                <botNavigationLinks>
                    <targetBotDialog>Direct_Transfer</targetBotDialog>
                </botNavigationLinks>
                <type>Redirect</type>
            </botNavigation>
            <type>Navigation</type>
        </botSteps>
        <developerName>Transfer_To_Agent</developerName>
        <label>Transfer with a question</label>
        <mlIntent>Transfer_To_Agent</mlIntent>
        <mlIntentTrainingEnabled>true</mlIntentTrainingEnabled>
        <showInFooterMenu>false</showInFooterMenu>
    </botDialogs>
</botVersions>
</Bot>

当前脚本

我遇到的问题是它将搜索整个树,所有 18 个版本,以查找 botDialogGrouplabel 元素,因为我使用的是 findall()。而我只希望它搜索最近的fullNamebotVersions,在这种情况下是“v18”。

手动输入“v18”不是问题,因为我总是知道要查找的版本。而且它很有用,因为不同的机器人有不同的版本。

import xml.etree.ElementTree as ET
import pandas as pd

cols = ["BotVersion", "DialogGroup", "Dialog"]
rows = []

tree = ET.parse('ChattyBot.xml')
root = tree.getroot()

for fullName in root.findall(".//fullName[.='v18']"):
    for botDialogGroup in root.findall(".//botDialogGroup"):
        for label in root.findall(".//label"):
            print(fullName.text, botDialogGroup.text, label.text)
            rows.append({"BotVersion": fullName.text,
            "DialogGroup": botDialogGroup.text,
            "Dialog": label.text})

df = pd.DataFrame(rows, columns=cols)

df.to_csv("botcsvfile.csv")

使用 pandas 将所需的最终结果保存到 csv 文件。

BotVersion DialogGroup Dialog
v18 Transfer Transfer with a question

【问题讨论】:

  • 如果最新版本是 v19 怎么办?需要修改代码吗?
  • @balderman 是的,我会下载一个新的 XML 文件,将版本更改为“v19”,然后运行脚本。这是意料之中的,因为有多个机器人都有不同数量的版本。
  • xml 可以包含多少个类似&lt;fullName&gt;vXY&lt;/fullName&gt; 的条目?当前代码有什么问题?
  • 每个版本有一个条目。我认为可以创建的版本数量没有限制。
  • 当前代码的问题如上所述,我有 18 个版本,它将在所有 18 个版本中搜索 botDialogGrouplabel 元素。我只希望它搜索这些元素的最新版本,在本例中为版本 18。

标签: python xml elementtree


【解决方案1】:

好的,这段代码假设您的 XML 将采用 version, dialog1, dialog2, dialog3, version2, dialog1, dialog2, etc... 的模式,如果不是这种情况,请告诉我,我将重新评估代码。但基本上循环代码并创建对话框组,然后按版本号排序。之后展平以获得嵌套列表表单以创建熊猫数据框。

import xml.etree.ElementTree as ET
import pandas as pd

cols = ["BotVersion", "DialogGroup", "Dialog"]
rows = []

tree = ET.parse('test.xml')
root = tree.getroot()


for fullName in root.findall(".//botVersions"):
    versions = list(fullName)

# creating the many to one relation between the versions and bot dialogs
grouping = []
relations = []
for i, tag in enumerate(versions):
    if i == 0:
        relations.append(tag)
    elif tag.tag == 'fullName':
        grouping.append(relations)
        relations = []
        relations.append(tag)
    else:
        relations.append(tag)
        # edge case for end of list)
        if i == len(versions) - 1:
            grouping.append(relations)

#sorting by the text of the fullName tag to be able to slice the end for latest version
grouping.sort(key=lambda x: x[0].text)
rows = grouping[-1]

#flatening the text into rows for the pandas dataframe
version_number = rows[0].text
pandas_row = [version_number]
pandas_rows = []
for r in rows[1:]:
    pandas_row = [version_number]
    for child in r.iter():
        if child.tag in ['botDialogGroup', 'label']:
            pandas_row.append(child.text)
    pandas_rows.append(pandas_row)

df = pd.DataFrame(pandas_rows, columns=cols)
print(df)

【讨论】:

    【解决方案2】:
    from lxml import etree
    
    bots = """your xml above"""
    cols = ["BotVersion", "DialogGroup", "Dialog"]
    rows = []
    ver = 'v18'
    
    root = etree.XML(bots)
    
    for entry in root.xpath(f"//botVersions[//fullName[.='{ver}']]"):
        rows.append([ver,entry.xpath('//botDialogGroup/text()')[0],entry.xpath('//label/text()')[0]])
    df = pd.DataFrame(rows, columns=cols)
    df
    

    输出应该是您预期的 df。

    【讨论】:

    • 谢谢,这非常适合我在上面发布的 sn-p,但是当我尝试使用 200K+ XML 文件而不是小的 sn-p 时,我收到一条错误消息,指出“lxml.etree. XMLSyntaxError: 需要开始标记,'
    • @MyNameHere 恐怕我无能为力 - 显然,我无权访问您的实际 xml;我只能假设问题中的xml是原始的代表性样本。
    • 我假设它会起作用,因为格式应该是相同的。这是另一个较小文件的 pastebin。 pastebin.com/aaDfBPUm
    • @MyNameHere 首先,该名称具有必须单独处理的名称空间。其次,它有不同的元素(&lt;developerName> 和 &lt;label&gt;,所以你必须修改它的预期输出。另外,仅供参考,它是 v3 而不是 v18
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-11
    • 2014-05-30
    • 1970-01-01
    • 2023-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多