Разбор XML с помощью Python — доступ к элементам

Я использую lxml для анализа некоторого xml, но по какой-то причине я не могу найти определенный элемент.

Я пытаюсь получить доступ к элементам <Constant>.

Вот фрагмент xml:

  </rdf:Description>
</rdf:RDF>
        </MiriamAnnotation>
        <ListOfSubstrates>
          <Substrate metabolite="Metabolite_5" stoichiometry="1"/>
        </ListOfSubstrates>
        <ListOfModifiers>
          <Modifier metabolite="Metabolite_9" stoichiometry="1"/>
        </ListOfModifiers>
        <ListOfConstants>
          <Constant key="Parameter_4344" name="Kcat" value="433.724"/>
          <Constant key="Parameter_4343" name="km" value="479.617"/>

Код, который я использую, выглядит следующим образом:

    >>> from lxml import etree as ET
    >>> parsed = ET.parse('ct.cps')
    >>> root = parsed.getroot()    
    >>> for a in root.findall(".//Constant"):
    ...     print a.attrib['key']
    ... 
    >>> for a in root.findall('Constant'):
    ...     print a.get('key')
    ... 
    >>> for a in root.findall('Constant'):
    ...     print a.attrib['key']
    ... 

Как видите, ни одна из этих вещей не работает.

Что я делаю не так?


РЕДАКТИРОВАТЬ: мне интересно, связано ли это с тем, что элементы <Constant> пусты?


EDIT2: исходный файл xml здесь: https://www.dropbox.com/s/i6hga7nvmcd6rxx/ct.cps?dl=0


person Charon    schedule 02.07.2015    source источник
comment
Я думаю, это связано с пространством имен. Вам нужно позаботиться о части пространства имен.   -  person Hai Vu    schedule 02.07.2015
comment
Ах, я понимаю, что вы имеете в виду, я попробую.   -  person Charon    schedule 02.07.2015


Ответы (3)


Вот как вы можете получить значения, которые вы ищете:

from lxml import etree

parsed = etree.parse('ct.cps')

for a in parsed.findall("//{http://www.copasi.org/static/schema}Constant"):
    print a.attrib["key"]

Выход:

Parameter_4344
Parameter_4343
Parameter_4342
Parameter_4341
Parameter_4340
Parameter_4339
Parameter_4338
Parameter_4337
Parameter_4336
Parameter_4335
Parameter_4334
Parameter_4333
Parameter_4332
Parameter_4331
Parameter_4330
Parameter_4329
Parameter_4328
Parameter_4327
Parameter_4326
Parameter_4325
Parameter_4324
Parameter_4323
Parameter_4322
Parameter_4321
Parameter_4320
Parameter_4319

Здесь важно то, что корневой элемент COPASI в вашем файле XML (настоящий в URL-адресе Dropbox) объявляет пространство имен по умолчанию (http://www.copasi.org/static/schema). Это означает, что элемент и все его потомки, включая Constant, принадлежат этому пространству имен.

Таким образом, вместо Constant элементов вам нужно искать {http://www.copasi.org/static/schema}Constant элементов.

См. http://lxml.de/tutorial.html#пространстваимен.


Вот как это можно сделать, используя XPath вместо findall:

from lxml import etree

NSMAP = {"c": "http://www.copasi.org/static/schema"}

parsed = etree.parse('ct.cps')

for a in parsed.xpath("//c:Constant", namespaces=NSMAP):
    print a.attrib["key"]

См. http://lxml.de/xpathxslt.html#namespaces-and-prefixes.

person mzjn    schedule 04.07.2015
comment
Большое вам спасибо. - person Charon; 04.07.2015
comment
Ух ты!! У меня была раздражающая проблема с синтаксическим анализом с нескольких часов, и по чистой случайности я обнаружил, что источником моей проблемы является именно пространство имен! Спасибо большое! - person user5193682; 12.08.2017
comment
@user9589: Я рад, что смог помочь! - person mzjn; 12.08.2017

Во-первых, пожалуйста, не обращайте внимания на мой комментарий. Оказывается, xml.etree намного лучше стандартного xml.etree.ElementTree в том смысле, что он заботится о пространстве имен. Проблема в том, что вы хотите найти '//Constant', что означает, что узлы могут быть на любом уровне. Однако корневой элемент не позволяет вам это сделать:

>>> root.findall('//Constant')
SyntaxError: cannot use absolute path on element

Однако вы можете сделать это на более высоком уровне:

>>> parsed.findall('//Constant')
[<Element Constant at 0x10a7ce128>, <Element Constant at 0x10a7ce170>]

Обновлять

Выкладываю здесь полный текст. Поскольку у меня нет вашего полного XML-файла, я кое-что придумаю, чтобы заполнить пробел.

from lxml import etree as ET
from StringIO import StringIO

xml_text = """<?xml version='1.0' encoding='utf-8' ?>

<rdf:root  xmlns:rdf='http://foo.bar.com/rdf'>
<rdf:RDF>
  <rdf:Description>
    DescriptionX
  </rdf:Description>
</rdf:RDF>
<rdf:foo>
        <MiriamAnnotation>
          bar
        </MiriamAnnotation>
        <ListOfSubstrates>
          <Substrate metabolite="Metabolite_5" stoichiometry="1"/>
        </ListOfSubstrates>
        <ListOfModifiers>
          <Modifier metabolite="Metabolite_9" stoichiometry="1"/>
        </ListOfModifiers>
        <ListOfConstants>
          <Constant key="Parameter_4344" name="Kcat" value="433.724"/>
          <Constant key="Parameter_4343" name="km" value="479.617"/>
        </ListOfConstants>
</rdf:foo>
</rdf:root>
"""

buffer = StringIO(xml_text)
tree = ET.parse(buffer)
for constant_node in tree.findall('//Constant'):
    print constant_node.attrib['key']
person Hai Vu    schedule 02.07.2015
comment
Я думал, что lxml лучше, чем elementtree, в отношении пространств имен, потому что у меня были ошибки пространства имен при использовании последнего, и теперь они отсортированы. Однако первая ошибка удаляется путем помещения . перед первой обратной косой чертой... и это все равно не работает. Что касается второго предложения, я все еще не могу получить доступ к значениям атрибутов с помощью этого кода: ничего не выводится - person Charon; 02.07.2015
comment
Я думаю, что это может быть как-то связано с тем, что элементы <Constant> являются пустыми элементами, но я не уверен. - person Charon; 02.07.2015

Не используйте findall. Он имеет ограниченный набор функций и предназначен для совместимости с ElementTree.

Вместо этого используйте xpath, который поддерживает пространства имен. Из вышесказанного кажется, что вы, вероятно, хотите сказать что-то вроде

# possibilities, you need to get these right...
ns_dict = {'atom':"http://www.w3.org/2005/Atom",,
    "rdf":"http://www.w3.org/2000/01/rdf-schema#" }

root = parsed.getroot()    
for a in root.xpath('.//rdf:Constant', namespaces=ns_dict):
    print a.attrib['key']

Обратите внимание, что вы должны включать префикс пространства имен в выражение xpath всякий раз, когда элемент имеет непустое пространство имен, и они должны сопоставляться с одним из URL-адресов пространства имен, которые соответствуют тем же URL-адресам в вашем документе.

Обновлять

Поскольку вы разместили свой исходный документ, я вижу, что для элементов, которые вы ищете, не назначено пространство имен. Это будет работать, я только что попробовал это с вашим исходным документом:

for a in tree.xpath("//Constant"):
    print a.attrib['key']

Вам не нужно пространство имен, потому что в самом документе не указано пространство имен по умолчанию.

person Gary Wisniewski    schedule 02.07.2015
comment
Спасибо. Как узнать пространство имен элемента? РЕДАКТИРОВАТЬ: я уверен, что rdf - это пространство имен, но ваш код все еще не работает - person Charon; 02.07.2015
comment
Обратите внимание, что rdf — это просто префикс пространства имен. Мой rdf:Constant относится к пространству имен в ns_dict. Пространства имен должны быть явно объявлены внутри вашего исходного документа и наследуются подэлементами. Ищите объявления xmlns в содержащих элементах. Например, если содержащий элемент имеет xmlns=, он определяет пространство имен по умолчанию для любого неукрашенного подэлемента, тогда как xmlns:xxx= определяет пространство имен для префикса 'xxx'. Кроме того, не имеет значения, какой префикс был в вашем исходном документе. lxml переписывает их все внутренне, поэтому вам нужно их переназначить. - person Gary Wisniewski; 02.07.2015
comment
Итак, термин rdf будет URL-адресом? - person Charon; 02.07.2015
comment
Да, пространства имен указываются как URL-адреса в атрибутах xmlns исходного документа. Префиксы — это просто сопоставления, и префиксы исходного документа не будут сохранены. ET.parse преобразует все префиксы во внутренние, явные ссылки на URL-адреса пространства имен, и вам необходимо использовать карту пространства имен, такую ​​как ns_dict в моем примере, чтобы восстановить новый набор префиксов для использования в xpath. - person Gary Wisniewski; 03.07.2015
comment
Вы можете получить список пространств имен, используемых вашим исходным документом, используя parsed.getroot().nsmap. Если вы распечатаете это, вы увидите, какие пространства имен определены в вашем документе. - person Gary Wisniewski; 03.07.2015
comment
ХОРОШО. Спасибо за помощь. Поэтому мне нужно использовать один и тот же URL-адрес пространства имен на карте и в термине rdf перед нужным мне элементом; должен ли URL-адрес карты пространства имен быть URL-адресом из моего источника? - person Charon; 03.07.2015
comment
parsed.getroot().nsmap дает только одно пространство имен: >>> print parsed.getroot().nsmap {None: 'http://www.copasi.org/static/schema'} - person Charon; 03.07.2015
comment
Смотрите мое обновление выше. Пространства имен ведут себя так, как я описал, но теперь, когда я вижу ваш исходный документ, становится очевидным, что элементы, которые вы ищете, не имеют пространства имен, и ни одному содержащему элементу не назначено пространство имен по умолчанию. - person Gary Wisniewski; 03.07.2015
comment
Извините, я еще не опубликовал исходный документ - это был только URL-адрес пространства имен. Источник находится здесь: dropbox.com/s/i6hga7nvmcd6rxx/ct.cps? дл=0 - person Charon; 03.07.2015
comment
К сожалению, код не работает - я не получаю вывод - person Charon; 03.07.2015
comment
Я бы подозревал какую-то другую проблему. Используя Python 2.7, я вырезал и вставил ваш код точно из приведенного ниже обновления, а затем запустил код в своем обновлении. Я получил две строки, одна сказала Parameter_4344 другая сказала Parameter_4343. - person Gary Wisniewski; 03.07.2015
comment
Код обновления работает только с фрагментом, а не с исходным кодом, который я разместил. - person Charon; 03.07.2015
comment
в самом документе не указано пространство имен по умолчанию. Это не правильно. Корневой элемент COPASI в файле XML по URL-адресу Dropbox объявляет пространство имен. - person mzjn; 04.07.2015