Поиск и удаление элемента с elementTree в Python - PullRequest
17 голосов
/ 27 июля 2011

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

Однако я не могу получить доступ к родительскому элементу, чтобы удалить его

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"

props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
    type = prop.attrib.get('type', None)
    if type == 'json':
        value = json.loads(prop.attrib['value'])
        if value['name'] == 'Page1.Button1':
            #here I need to access the parent of prop
            # in order to delete the prop

Есть ли способ, которым я могу это сделать?

Спасибо

Ответы [ 6 ]

20 голосов
/ 27 июля 2011

Дочерние элементы можно удалить с помощью метода remove.Чтобы удалить элемент, вы должны вызвать его родительский метод remove.К сожалению, Element не предоставляет ссылку на своих родителей, поэтому вы должны отслеживать отношения между родителями и детьми (что говорит против использования elem.findall())

Предлагаемое решение может выглядетьэто:

root = elem.getroot()
for child in root:
    if child.name != "prop":
        continue
    if True:# TODO: do your check here!
        root.remove(child)

PS: не используйте prop.attrib.get(), используйте prop.get(), как объяснено здесь .

2 голосов
/ 01 июня 2013

Вы можете использовать xpath для выбора родителя элемента.

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"

props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
    type = prop.get('type', None)
    if type == 'json':
        value = json.loads(prop.attrib['value'])
        if value['name'] == 'Page1.Button1':
            # Get parent and remove this prop
            parent = prop.find("..")
            parent.remove(prop)

http://docs.python.org/2/library/xml.etree.elementtree.html#supported-xpath-syntax

За исключением , если вы попытаетесь, чтобы это не сработало: http://elmpowered.skawaii.net/?p=74

Так что вместо этого вы должны:

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"
search = './/{0}prop'.format(namespace)

# Use xpath to get all parents of props    
prop_parents = elem.findall(search + '/..')
for parent in prop_parents:
    # Still have to find and iterate through child props
    for prop in parent.findall(search):
        type = prop.get('type', None)
        if type == 'json':
            value = json.loads(prop.attrib['value'])
            if value['name'] == 'Page1.Button1':
                parent.remove(prop)

Это два поиска и вложенный цикл. Внутренний поиск выполняется только для элементов, которые, как известно, содержат реквизиты в качестве первых потомков, но это может не иметь большого значения в зависимости от вашей схемы.

1 голос
/ 13 августа 2016

Используя тот факт, что у каждого ребенка должен быть родитель, я собираюсь упростить пример @ kitsu.eb. Если использовать команду findall, чтобы получить детей и родителей, их индексы будут эквивалентны.

    file = open('test.xml', "r")
    elem = ElementTree.parse(file)

    namespace = "{http://somens}"
    search = './/{0}prop'.format(namespace)

    # Use xpath to get all parents of props    
    prop_parents = elem.findall(search + '/..')

    props = elem.findall('.//{0}prop'.format(namespace))
    for prop in props:
            type = prop.attrib.get('type', None)
            if type == 'json':
                value = json.loads(prop.attrib['value'])
                if value['name'] == 'Page1.Button1':
                    #use the index of the current child to find
                    #its parent and remove the child
                    prop_parents[props.index[prop]].remove(prop)
0 голосов
/ 03 апреля 2018

Решение с использованием модуля lxml

from lxml import etree

root = ET.fromstring(xml_str)
for e in root.findall('.//{http://some.name.space}node'):
parent = e.getparent()
for child in parent.find('./{http://some.name.space}node'):
    try:
        parent.remove(child)
    except ValueError:
        pass
0 голосов
/ 06 февраля 2018

Мне нравится использовать выражение XPath для такого рода фильтрации.Если я не знаю иначе, такое выражение должно применяться на корневом уровне, что означает, что я не могу просто получить родителя и применить одно и то же выражение к этому родителю.Однако мне кажется, что есть хорошее и гибкое решение, которое должно работать с любым поддерживаемым XPath, если ни один из искомых узлов не является корневым.Это выглядит примерно так:

root = elem.getroot()
# Find all nodes matching the filter string (flt)
nodes = root.findall(flt)
while len(nodes):
    # As long as there are nodes, there should be parents
    # Get the first of all parents to the found nodes
    parent = root.findall(flt+'/..')[0]
    # Use this parent to remove the first node
    parent.remove(nodes[0])
    # Find all remaining nodes
    nodes = root.findall(flt)
0 голосов
/ 23 августа 2017

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

1) Он не обрабатывает несколько вложенных уровней тегов.

2) Он сломается, если несколько тегов XML будут удалены в одном и том жеуровень один за другим.Поскольку каждый элемент имеет индекс Element._children, вы не должны удалять его при итерации вперед.

Я думаю, что лучшее, более универсальное решение заключается в следующем:

import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)
root = tree.getroot()

def iterator(parents, nested=False):
    for child in reversed(parents):
        if nested:
            if len(child) >= 1:
                iterator(child)
        if True:  # Add your entire condition here
            parents.remove(child)

iterator(root, nested=True)

Для OP это должноработать - но у меня нет данных, с которыми вы работаете, чтобы проверить, идеально ли это.

import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)

namespace = "{http://somens}"
props = tree.findall('.//{0}prop'.format(namespace))

def iterator(parents, nested=False):
    for child in reversed(parents):
        if nested:
            if len(child) >= 1:
                iterator(child)
        if prop.attrib.get('type') == 'json':
            value = json.loads(prop.attrib['value'])
            if value['name'] == 'Page1.Button1':
                parents.remove(child)

iterator(props, nested=True)
...