合并具有相同标签的所有元素(如果它们具有相同的父元素)

Merge all the elements with the same tag if they have same parent

我正在使用以下示例 xml 树:

<group>
   <group_info>
      <Text>
         Text_1
      </Text>
   </group_info>
   <group_info>
      <Text>
         Text_2
      </Text>
   </group_info>
   <group_info>
      <Text>
         Text_3
      </Text>
   </group_info>
</group>

我想合并 <group> 中所有重复的子元素,并将它们组合成一个子元素。我想要的输出:

<group>
   <group_info>
      <Text>
         Text_1 Text_2 Text_3
      </Text>
   </group_info>
</group>

在不导入任何新模块的情况下,我正在使用:

import xml.etree.ElementTree
group_list = MY_XML.findall(".//group") # I do this because the actual xml is bigger with several groups 
for elem in group_list:
    string_text = ""
    for child in elem :
        for super_child in child:
            if(super_child.text is not None): #Just in case None value because I cannot use string addition
                string_text = string_text + super_child.text + " "
        elem.remove(child)
    new_child = xml.etree.ElementTree.Element("group_info")
    text_elem = xml.etree.ElementTree.Element("Text")
    text_elem.text = string_text
    new_child.append(text_elem)
    elem.append(new_child)

我的想法是遍历我的所有组,将 <group_info> 中的所有文本信息收集到一个字符串中,然后删除我的树中的所有这些元素并附加一个带有信息的新元素。这可能不是最好的方法,但我相对较新。但是我的输出看起来像:

<group>
   <group_info>
      <Text>
         Text_1
      </Text>
   </group_info>
   <group_info>
      <Text>
         Text_2
      </Text>
   </group_info>
   <group_info>
      <Text>
         Text_3
      </Text>
   </group_info>
<group_info><Text>Text1 Text2 Text3</Text></group_info></group>

从技术上讲,最后一行是我需要的(虽然它看起来不漂亮)但我不知道为什么它没有删除其他不需要的 <group_info> 即使我调用 elem.remove(child)

由于没有人出来回答,我花了一些时间,但如果其他人 运行 遇到同样的问题,我有答案和指点。

我从你在问题中看到的初始代码开始,它不会删除旧元素并留下非常难看的最后一行。

注意:MY_XML = xml.etree.ElementTree.parse({PATH_OF_XML})

  1. 如果你正在使用xml.etree.ElementTree,你应该使用remove() 方法来删除一个节点,但这需要你有父节点 节点参考。我称之为 elem.remove(child) [第 9 行]

  2. 那么,为什么它们没有被删除?我发现修改 您正在迭代的对象会影响迭代。这不是 完全出乎意料,如果你改变一个列表,它是一样的 迭代它。我无法存储信息,并且 在一次迭代中删除元素。

我不得不拆分任务:

group_list = MY_XML.findall(".//group") # I do this because the actual xml is bigger with several groups
text_list = [] 
for group in group_list:
    string_text = ""
    for child in group :
        for super_child in child:
            if(super_child.text is not None): #Just in case None value because I cannot use string addition
                string_text = string_text + super_child.text + " "
    text_list.append(string_text) #I stored all the info in 1 group as a value in this list because like I stated my overall xml might be bigger with more than 1 group

for group in group_list:
    for elem in group.findall(".//group_info"):
        #loop over all possible <group> and removes all <group_info> inside
        group.remove(elem) 

#And finally to append the information gathered:
for group in group_list:
    Text_elem = ET.Element("Text")
    Text_elem.text = text_list[group_list.index(group)]
    group_info_elem = ET.Element("Kundenhinweis_redigiert")
    group_info_elem.append(Text_elem)
    group.append(Kund_elem)

这给我留下了非常难看的输出:

<group>
<group_info><Text>Text1 Text2 Text3</Text></group_info></group>

使用 xml.dom.minidom 模块很容易解决。我首先定义:

def prettify(elem):
    rough_string = xml.etree.ElementTree.tostring(elem, 'utf-8')
    reparsed = xml.dom.minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="  ")

调用函数:

root = MY_XML.getroot()
pretty_xml = prettify(root)
#Next line is optional, but sometimes your string contains empty lines or lines with white spaces and/or breaklines
pretty_xml = "\n".join([s for s in pretty_xml.split("\n") if not s.isspace()])
print(pretty_xml)

输出将是:

<group>
    <group_info>
        <Text>Text1 Text2 Text3</Text>
    </group_info>
</group>

希望这对其他新人有所帮助。

考虑 XSLT, the special-purpose language designed to transform XML files, where you can run the Muenchian Method 通过索引相同的节点名称并将它们的文本值分组。虽然 Python 的内置 xml.ElementTree 不支持 XSLT,但其第三方模块 lxml 支持 XSLT 1.0 脚本。而且您可以这样做,而无需单个 for 循环或必须手动 美化 输出。

XSLT (另存为.xsl文件,一个特殊的.xml文件)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="xml"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="group_key" match="group/*" use="name()" />

    <xsl:template match="/group">            
        <xsl:copy>
            <xsl:apply-templates select="*[generate-id() =
                                   generate-id(key('group_key', name())[1])]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="group/*">
        <xsl:copy>
            <xsl:element name="{name(*)}">
            <xsl:for-each select="key('group_key', name())">
                <xsl:value-of select="normalize-space(*)"/>
                <xsl:if test="position() != last()">
                    <xsl:text> </xsl:text>
                </xsl:if>
            </xsl:for-each>
            </xsl:element>
        </xsl:copy>
    </xsl:template>            
</xsl:stylesheet>

XSLT Demo (两组演示)

Python

import lxml.etree as et

# LOAD XML AND XSL
doc = et.parse('/path/to/Input.xml')
xsl = et.parse('/path/to/XSLT_Script.xsl')

# CONFIGURE TRANSFORMER
transform = et.XSLT(xsl)    

# RUN TRANSFORMATION
result = transform(doc)

# PRINT RESULT
print(result)  

# SAVE TO FILE
with open('/path/to/Output.xml', 'wb') as f:
   f.write(result)