Объединить все элементы с одинаковым тегом, если у них один и тот же родитель - PullRequest
0 голосов
/ 30 августа 2018

Я работаю со следующим примером дерева 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)

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Рассмотрим XSLT , язык специального назначения, предназначенный для преобразования XML-файлов, где вы можете запустить 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)
0 голосов
/ 04 сентября 2018

Поскольку никто не пришел, чтобы ответить, мне потребовалось некоторое время, но у меня есть ответы и указания, если другие люди столкнутся с той же проблемой.

Я начал с исходного кода, который вы видите в вопросе, который не будет удалять старые элементы, и оставил очень некрасивую последнюю строку.

Обратите внимание: 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>

Надеюсь, это поможет другим новичкам.

...