Разбор на 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> елемента са празни?


РЕДАКТИРАНЕ 2: Източник 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#namespaces.


Ето как можете да го направите, като използвате 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? dl=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