Использование XPath в Python с LXML

У меня есть скрипт python, используемый для анализа XML и экспорта в файл csv определенных интересующих элементов. Я попытался теперь изменить сценарий, чтобы разрешить фильтрацию XML-файла в соответствии с критериями, эквивалентный запрос XPath будет:

\DC\Events\Confirmation[contains(TransactionId,"GTEREVIEW")]

Когда я пытаюсь использовать lxml для этого, мой код выглядит так:

xml_file = lxml.etree.parse(xml_file_path)
namespace = "{" + xml_file.getroot().nsmap[None] + "}"
node_list = xml_file.findall(namespace + "Events/" + namespace + "Confirmation[TransactionId='*GTEREVIEW*']")

Но, похоже, это не работает. Кто-нибудь может помочь? Пример файла XML:

<Events>
  <Confirmation>
    <TransactionId>GTEREVIEW2012</TransactionId>
  </Confirmation>    
  <Confirmation>
    <TransactionId>GTEDEF2012</TransactionId>
  </Confirmation>    
</Events> 

Поэтому мне нужны все узлы «Подтверждение», которые содержат идентификатор транзакции, который включает строку «GTEREVIEW». Спасибо


person naiminp    schedule 15.11.2016    source источник
comment
где ваш xml файл?   -  person SomeDude    schedule 15.11.2016
comment
Я обновил вопрос.   -  person naiminp    schedule 16.11.2016


Ответы (1)


findall() не поддерживает выражения XPath, только ElementPath (см. https://web.archive.org/web/20200504162744/http://effbot.org/zone/element-xpath.htm). ElementPath не поддерживает поиск элементов, содержащих определенную строку.

Почему вы не используете XPath? Предполагая, что файл test.xml содержит ваш образец XML, работает следующее:

> python
Python 2.7.9 (default, Jun 29 2016, 13:08:31) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> from lxml import etree
>>> tree=etree.parse("test.xml")
>>> tree.xpath("Confirmation[starts-with(TransactionId, 'GTEREVIEW')]")
[<Element Confirmation at 0x7f68b16c3c20>]

Если вы настаиваете на использовании findall(), лучшее, что вы можете сделать, это получить список всех Confirmation элементов, имеющих TransactionId дочерний узел:

>>> tree.findall("Confirmation[TransactionId]")
[<Element Confirmation at 0x7f68b16c3c20>, <Element Confirmation at 0x7f68b16c3ea8>]

Затем вам нужно отфильтровать этот список вручную, например:

>>> [e for e in tree.findall("Confirmation[TransactionId]")
     if e[0].text.startswith('GTEREVIEW')]
[<Element Confirmation at 0x7f68b16c3c20>]

Если ваш документ содержит пространства имен, следующее даст вам все Confirmation элементы, имеющие TransactionId дочерний узел, при условии, что элементы используют пространство имен по умолчанию (я использовал xmlns="file:xyz" в качестве пространства имен по умолчанию):

>>> tree.findall("//{{{0}}}Confirmation[{{{0}}}TransactionId]".format(tree.getroot().nsmap[None]))
[<Element {file:xyz}Confirmation at 0x7f534a85d1b8>, <Element {file:xyz}Confirmation at 0x7f534a85d128>]

И, конечно же, etree.ETXPath:

>>> find=etree.ETXPath("//{{{0}}}Confirmation[starts-with({{{0}}}TransactionId, 'GTEREVIEW')]".format(tree.getroot().nsmap[None]))
>>> find(tree)
[<Element {file:xyz}Confirmation at 0x7f534a85d1b8>]

Это позволяет комбинировать XPath и пространства имен.

person Markus    schedule 16.11.2016
comment
Большое спасибо за ответ! К сожалению, в моем документе задействовано пространство имен, в результате которого Xpath возвращает пустой список. После удаления пространства имен из файла код работает. Это можно обойти? По сути, файл начинается с ‹DC xmlns = tradefinder.db.com/Schemas/MEL/CapitaHorizon_0_9_2 .xsd xmlns: xs = w3.org/2001/XMLSchema ›И заканчивается с ‹/DC› - person naiminp; 16.11.2016
comment
Так и думал. Тогда вы все еще можете использовать второй подход с findall(). Вам просто нужно выполнить фильтрацию возвращенного списка узлов. - person Markus; 16.11.2016