web-dev-qa-db-fra.com

Rechercher et supprimer un élément avec elementTree en Python

J'ai un document XML dans lequel je veux rechercher certains éléments et s'ils correspondent à certains critères , Je souhaite les supprimer.

Cependant, je ne semble pas pouvoir accéder au parent de l'élément pour pouvoir le supprimer.

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

Est-ce qu'il y a un moyen de faire ça?

Merci

16
Thomas

Vous pouvez supprimer des éléments enfants à l'aide de la méthode correspondante remove. Pour supprimer un élément, vous devez appeler la méthode remove de ses parents. Malheureusement, Element ne fournit pas de référence à ses parents, il vous appartient donc de suivre les relations parent/enfant (ce qui contredit votre utilisation de elem.findall())

Une solution proposée pourrait ressembler à ceci:

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

PS: n'utilisez pas prop.attrib.get(), utilisez prop.get(), comme expliqué ici .

19
Constantinius

Vous pouvez utiliser xpath pour sélectionner le parent d'un élément.

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

Sauf si vous essayez que cela ne fonctionne pas: http://elmpowered.skawaii.net/?p=74

Donc, au lieu de cela, vous devez:

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)

C'est deux recherches et une boucle imbriquée. La recherche interne ne concerne que les éléments connus pour contenir des accessoires en tant que premiers enfants, mais cela peut ne pas vouloir dire grand chose, cela dépend de votre schéma.

2
kitsu.eb

En utilisant le fait que chaque enfant doit avoir un parent, je vais simplifier l'exemple de @ kitsu.eb. Si vous utilisez la commande findall pour obtenir les enfants et les parents, leurs index seront équivalents.

    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)
1
engineer14

Je sais que c’est un vieux fil conducteur, mais cela n’a pas cessé de surgir pendant que j’essayais de trouver une tâche similaire. Je n'ai pas aimé la réponse acceptée pour deux raisons:

1) Il ne gère pas plusieurs niveaux de balises imbriquées.

2) Cela se cassera si plusieurs balises XML sont supprimées au même niveau un après l'autre. Étant donné que chaque élément est un index de Element._children, vous ne devez pas le supprimer lors d'une itération en aval.

Je pense qu'une meilleure solution plus polyvalente est la suivante:

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)

Cela devrait fonctionner pour le PO, mais je n'ai pas les données avec lesquelles vous travaillez pour vérifier si elles sont parfaites.

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)
0
iceblueorbitz

J'aime utiliser une expression XPath pour ce type de filtrage. À moins que je sache le contraire, une telle expression doit être appliquée au niveau racine, ce qui signifie que je ne peux pas simplement obtenir un parent et appliquer la même expression à ce parent. Cependant, il me semble qu’il existe une solution agréable et flexible qui devrait fonctionner avec tout XPath pris en charge, tant qu’aucun des nœuds recherchés n’est la racine. Ca fait plutot comme ca:

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
Fredrik

Une solution utilisant le module 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
chi