Python и libxml2: как выполнять итерации в узлах xml с XPATH
У меня проблема с получением информации из дерева XML.
Мой XML имеет такую форму:
<?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>
Что я хочу сделать, это извлечь кортежи данных, как показано ниже:
[{'code': 'first', 'name': 'john'},
{'code': 'second', 'name': 'mike'},
{'code': 'third', 'name': 'albert'}]
Теперь я написал этот код 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})
Моя проблема в том, что у меня нет никакого результата, и у меня сложилось впечатление, что я делаю что-то не так с XPATH, когда я выполняю итерации на узлах.
Я также попытался с этими XPATH для идентификатора и имени:
/id
/name
/record/id
/record/name
/pre:id
/pre:name
и так далее, но с любым результатом (кстати, если я использую префикс в подзапросах, у меня есть ошибка).
Любая идея?
4 ответа
Вот предложение. Обратите внимание 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
Вы можете выбрать все необходимые элементы с помощью одного выражения XPath:
/pre:records/pre:record/*[self::pre:id or self::pre:name]
Затем просто обработайте выбранные узлы в Python.
Если возможно переключиться на lxml, вот один из способов сделать это:
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'}]
Создавая выражение XPath Димитра Новатчева, вы можете сделать это:
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)
Этот код libxml2 опирается на каждую запись, имеющую идентификатор и имя. Если id
или же name
отсутствует, ret_list
соединит неправильный идентификатор и имя, молча проваливаясь. При тех же обстоятельствах код lxml вызовет ошибку.
В libxslt по какой-то причине отсутствует такая важная поддержка пространства имен, но мы можем предварительно проанализировать файл xml, предварительно прочитать пространства имен из него и затем вызвать xsltproc с этими пространствами имен
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 ""