2010-07-29 7 views
2

J'ai un problème avec la récupération des informations d'un arbre XML.Python et libxml2: comment itérer dans les noeuds xml avec XPATH

Mon XML a cette forme:

<?xml version="1.0"?> 
<records xmlns="http://www.mysyte.com/foo"> 
    <record> 
    <id>first</id> 
    <name>john</name> 
    <papers> 
     <paper>john_1</paper> 
     <paper>john_2</paper> 
    </papers> 
    </record> 
    <record> 
    <id>second</id> 
    <name>mike</name> 
    <papers> 
     <paper>mike_a</paper> 
     <paper>mike_b</paper> 
    </papers> 
    </record> 
    <record> 
    <id>third</id> 
    <name>albert</name> 
    <papers> 
     <paper>paper of al</paper> 
     <paper>other paper</paper> 
    </papers> 
    </record> 
</records> 

Ce que je veux faire est d'extraire tuples de données telles que le suivi:

[{'code': 'first', 'name': 'john'}, 
{'code': 'second', 'name': 'mike'}, 
{'code': 'third', 'name': 'albert'}] 

Maintenant, je l'ai écrit ce code python:

try: 
    doc = libxml2.parseDoc(xml) 
except (libxml2.parserError, TypeError): 
    print "Problems loading XML" 

ctxt = doc.xpathNewContext() 
ctxt.xpathRegisterNs("pre", "http://www.mysyte.com/foo") 

record_nodes = ctxt.xpathEval('/pre:records/pre:record') 

for record_node in record_nodes: 
    id = record_node.xpathEval('id')[0].content 
    name = record_node.xpathEval('name')[0].content 
    ret_list.append({'code': id, 'name': name}) 

Mon problème est que je n'ai aucun résultat et j'ai l'impression de faire quelque chose de mal avec le XPATH w Quand je itére sur les noeuds.

J'ai aussi essayé avec ces XPath pour l'identifiant et le nom:

/id 
/name 
/record/id 
/record/name 
/pre:id 
/pre:name 

et ainsi de suite, mais avec un résultat (BTW si j'utilise le préfixe dans les requêtes sous-je une erreur).

Une idée?

Répondre

6

Voici une suggestion. Notez la méthode setContextNode():

import libxml2 

xml = "test.xml" 
doc = libxml2.parseFile(xml) 

ctxt = doc.xpathNewContext() 
ctxt.xpathRegisterNs("pre","http://www.mysyte.com/foo") 

ret_list = [] 
record_nodes = ctxt.xpathEval('/pre:records/pre:record') 

for node in record_nodes: 
    ctxt.setContextNode(node) 
    _id = ctxt.xpathEval('pre:id')[0].content 
    name = ctxt.xpathEval('pre:name')[0].content 
    ret_list.append({'code': _id, 'name': name}) 

print ret_list 
+0

Aucun commentaire sur ce produit? C'est en effet un moyen de "le faire directement dans libxml2". – mzjn

+0

Désolé! J'ai oublié de signer cette réponse comme la meilleure! Cela fonctionne réellement comme je le veux. Merci! –

0

S'il est possible de passer à lxml, voici une façon il pourrait être fait:

import lxml.etree as le 
root=le.XML(content) 
result=[] 
namespaces={'pre':'http://www.mysyte.com/foo'} 
for record in root: 
    id=record.xpath('pre:id',namespaces=namespaces)[0] 
    name=record.xpath('pre:name',namespaces=namespaces)[0] 
    result.append({'code':id.text,'name':name.text}) 
print(result) 
# [{'code': 'first', 'name': 'john'}, {'code': 'second', 'name': 'mike'}, {'code': 'third', 'name': 'albert'}] 

bâtiment hors de Dimitre Novatchev's XPath expression, vous pouvez le faire:

id_name_nodes = iter(ctxt.xpathEval('/pre:records/pre:record/*[self::pre:id or self::pre:name]')) 

ret_list=[] 
for id,name in zip(id_name_nodes,id_name_nodes): 
    ret_list.append({'code':id.content,'name':name.content}) 
print(ret_list) 

Ce code libxml2, repose sur chaque enregistrement ayant un identifiant et un nom. Si un id ou name est manquant, le ret_list associera le mauvais ID et nom, échouant silencieusement. Dans la même circonstance, le code lxml déclencherait une erreur.

+0

J'utilise libxml2 partout et je voudrais continuer à l'utiliser aussi dans ce cas. Cependant, merci pour votre réponse! –

+0

lxml utilise également la bibliothèque 'libxml2' (&' libxslt'). C'est essentiellement une couche sur le dessus pour rendre les choses difficiles comme ça facile. –

+0

ok, mais il devrait y avoir un moyen de le faire directement dans libxml2! –

0

Vous pouvez sélectionner tous les éléments dont vous avez besoin avec une seule expression XPath: Ensuite, il suffit processus

/pre:records/pre:record/*[self::pre:id or self::pre:name] 

les noeuds sélectionnés en python.

+0

Désolé mais cela ne répond pas à ma question –

+0

@ Giovanni-Di-Milia: Cela répond à la partie XPath - Je ne connais pas Python. Après avoir sélectionné tous les nœuds que vous voulez, vous devriez pouvoir les traiter en Python et produire le résultat souhaité. –

+0

Est-ce que cela garantit toute commande dans laquelle les nœuds sont retournés?Si ce n'est pas le cas, cela ajouterait une complication du côté python afin de garder trace de quel 'id 'appartient à' name'. –

-1

libxslt ne dispose pas d'un tel soutien important d'espace de noms pour une raison quelconque, mais nous pouvons pré-analyser le fichier xml, namespaces pré-lecture de celui-ci, puis appelez xsltproc avec les espaces de noms

def xpath(xml, xpathexpression): 
    f=open(xml) 
    fcontent = f.read() 
    f.close() 

    doc=libxml2.parseFile(xml) 
    xp = doc.xpathNewContext() 
    for nsdeclaration in re.findall('xmlns:*\w*="[^"]*"', fcontent): 
     m = re.match('xmlns:(\w+)=.*', nsdeclaration) 
     if m: 
      ns = m.group(1) 
     else: 
      ns = "default" 
     url = nsdeclaration[nsdeclaration.find('"')+1:nsdeclaration.rfind('"')] 
     xp.xpathRegisterNs(ns, url) 
    a=xp.xpathEval(xpathexpression) 
    if len(a): 
     return a[0].content 
    return "" 
+0

Je ne pense pas que cela répond aux questions ou ajoute quelque chose de plus à ce qui a déjà écrit –