web-dev-qa-db-fra.com

Analyse XML avec Python et minidom

J'utilise Python (minidom) pour analyser un fichier XML qui imprime une structure hiérarchique qui ressemble à ceci (l'indentation est utilisée ici pour montrer la relation hiérarchique significative):

My Document
Overview
    Basic Features
    About This Software
        Platforms Supported

Au lieu de cela, le programme itère plusieurs fois sur les nœuds et produit les éléments suivants, en imprimant les nœuds en double. (En regardant la liste des nœuds à chaque itération, il est évident pourquoi il fait cela, mais je n'arrive pas à trouver un moyen d'obtenir la liste des nœuds que je recherche.)

My Document
Overview
Basic Features
About This Software
Platforms Supported
Basic Features
About This Software
Platforms Supported
Platforms Supported

Voici le fichier source XML:

<?xml version="1.0" encoding="UTF-8"?>
<DOCMAP>
    <Topic Target="ALL">
        <Title>My Document</Title>
    </Topic>
    <Topic Target="ALL">
        <Title>Overview</Title>
        <Topic Target="ALL">
            <Title>Basic Features</Title>
        </Topic>
        <Topic Target="ALL">
            <Title>About This Software</Title>
            <Topic Target="ALL">
                <Title>Platforms Supported</Title>
            </Topic>
        </Topic>
    </Topic>
</DOCMAP>

Voici le programme Python:

import xml.dom.minidom
from xml.dom.minidom import Node

dom = xml.dom.minidom.parse("test.xml")
Topic=dom.getElementsByTagName('Topic')
i = 0
for node in Topic:
    alist=node.getElementsByTagName('Title')
    for a in alist:
        Title= a.firstChild.data
        print Title

Je pourrais résoudre le problème en n'imbriquant pas les éléments 'Topic', en changeant les noms de sujet de niveau inférieur en quelque chose comme 'SubTopic1' et 'SubTopic2'. Mais, je veux profiter de la structuration hiérarchique XML intégrée sans avoir besoin de noms d'éléments différents; il semble que je devrais être en mesure d'imbriquer des éléments "Topic" et qu'il devrait y avoir un moyen de savoir à quel niveau "Topic" je regarde actuellement.

J'ai essayé un certain nombre de fonctions XPath différentes sans grand succès.

16
hWorks

getElementsByTagName est récursif, vous obtiendrez tous descendants avec un tagName correspondant. Parce que vos sujets contiennent d'autres sujets qui ont également des titres, l'appel obtiendra plusieurs fois les titres inférieurs.

Si vous souhaitez demander uniquement tous les enfants directs correspondants et que vous n'avez pas XPath disponible, vous pouvez écrire un filtre simple, par exemple:

def getChildrenByTagName(node, tagName):
    for child in node.childNodes:
        if child.nodeType==child.ELEMENT_NODE and (tagName=='*' or child.tagName==tagName):
            yield child

for topic in document.getElementsByTagName('Topic'):
    title= list(getChildrenByTagName('Title'))[0]         # or just get(...).next()
    print title.firstChild.data
9
bobince

Permettez-moi de mettre ce commentaire ici ...

Merci pour la tentative. Ça n'a pas marché mais ça m'a donné quelques idées. Les travaux suivants (la même idée générale; FWIW, le nodeType est ELEMENT_NODE):

import xml.dom.minidom
from xml.dom.minidom import Node

dom = xml.dom.minidom.parse("docmap.xml")

def getChildrenByTitle(node):
    for child in node.childNodes:
        if child.localName=='Title':
            yield child

Topic=dom.getElementsByTagName('Topic')
for node in Topic:
    alist=getChildrenByTitle(node)
    for a in alist:
#        Title= a.firstChild.data
        Title= a.childNodes[0].nodeValue
        print Title
7
hWorks

Je pense que cela peut aider

import os
import sys
import subprocess
import base64,xml.dom.minidom
from xml.dom.minidom import Node
f = open("file.xml",'r')
data = f.read()
i = 0
doc = xml.dom.minidom.parseString(data)
for topic in doc.getElementsByTagName('Topic'):
   title= doc.getElementsByTagName('Title')[i].firstChild.nodeValue
   print title
   i +=1

Sortie:

My Document
Overview
Basic Features
About This Software
Platforms Supported
4
0x3bfc

Vous pouvez utiliser le générateur suivant pour parcourir la liste et obtenir des titres avec des niveaux d'indentation:

def f(elem, level=-1):
    if elem.nodeName == "Title":
        yield elem.childNodes[0].nodeValue, level
    Elif elem.nodeType == elem.ELEMENT_NODE:
        for child in elem.childNodes:
            for e, l in f(child, level + 1):
                yield e, l

Si vous le testez avec votre fichier:

import xml.dom.minidom as minidom
doc = minidom.parse("test.xml")
list(f(doc))

vous obtiendrez une liste avec les tuples suivants:

(u'My Document', 1), 
(u'Overview', 1), 
(u'Basic Features', 2), 
(u'About This Software', 2), 
(u'Platforms Supported', 3)

Ce n'est qu'une idée de base à affiner bien sûr. Si vous voulez juste des espaces au début, vous pouvez coder cela directement dans le générateur, mais avec le niveau, vous avez plus de flexibilité. Vous pouvez également détecter automatiquement le premier niveau (ici, c'est juste un mauvais travail d'initialiser le niveau à -1 ...).

3
RedGlyph

Fonction récursive:

import xml.dom.minidom

def traverseTree(document, depth=0):
  tag = document.tagName
  for child in document.childNodes:
    if child.nodeType == child.TEXT_NODE:
      if document.tagName == 'Title':
        print depth*'    ', child.data
    if child.nodeType == xml.dom.Node.ELEMENT_NODE:
      traverseTree(child, depth+1)

filename = 'sample.xml'
dom = xml.dom.minidom.parse(filename)
traverseTree(dom.documentElement)

Votre xml:

<?xml version="1.0" encoding="UTF-8"?>
<DOCMAP>
    <Topic Target="ALL">
        <Title>My Document</Title>
    </Topic>
    <Topic Target="ALL">
        <Title>Overview</Title>
        <Topic Target="ALL">
            <Title>Basic Features</Title>
        </Topic>
        <Topic Target="ALL">
            <Title>About This Software</Title>
            <Topic Target="ALL">
                <Title>Platforms Supported</Title>
            </Topic>
        </Topic>
    </Topic>
</DOCMAP>

Votre sortie souhaitée:

 $ python parse_sample.py 
      My Document
      Overview
          Basic Features
          About This Software
              Platforms Supported
2
imesias