2017/02/13

Przeszukiwanie dokumentów XML przy pomocy XPath

Przeszukiwanie dokumentów XML przy pomocy XPath

Dokumenty XML są standardem przechowywania i przekazywania danych. Xpath natomiast jest jedną z podstawowych metod przeszukiwania XMLi. Jak więc w Pythonie przeszukiwać xmle?
Python posiada w bibliotece standardowej parser XML - moduł xml. Jednak moduł ten ma ograniczone wsparcie dla XPath (zobacz xpath support na docs.python.org). Bogatsze wsparcie dla XPath zapewnia biblioteka lxml, oparta na libxml2 i libxslt - dobrze rozwiniętych i dostępnych na wiele platform bibliotek. lxml można łatwo zainstalować przy pomocy pip-a, jest wśród standardowych pakietów anacondy, a ze względu na zależności często jest zainstalowany także na linuxowych systemach (zależy od niego np. Inkscape i Cinnamon).
Poniższy program, kompatybilny z Python 3, przedstawia przykłady użycia lxml do parsowania XML z wykorzystaniem XPath.

'''przykladowy program parsujacy XMLe'''
from lxml import etree

# przykladowy xml wczytany bezposrednio z lancucha bajtow.
xml = etree.fromstring(b'''<?xml version="1.0" encoding="UTF-8"?>
<ksiazki>
  <ksiazka>
    <tytul lang="pl">Pan Tadeusz</tytul>
    <autor>A. Mickiewicz</autor>
    <rok>2012</rok>
    <cena>31.99</cena>
  </ksiazka>
  <ksiazka>
    <tytul lang="pl">Antygona</tytul>
    <autor>Sofokles</autor>
    <rok>2006</rok>
    <cena>5.99</cena>
  </ksiazka>
  <f:ksiazka xmlns:f="https://wrobelmaciek.blogspot.com/ksiazki">
    <f:tytul lang="pl">Emocjonalne wampiry w pracy</f:tytul>
    <f:autor>A. B. Bernstein</f:autor>
    <f:rok>2014</f:rok>
    <f:cena>37.90</f:cena>
  </f:ksiazka>
</ksiazki>
''')

#metoda xpath zwraca liste elementow spelniajacych kryteria
#elementy sa objektami, mozna odczytac ich tekst, wziac przodka, 
#atrybuty itd.
print('autorzy ksiazek i jezyk tytulu')
for el in xml.xpath('.//autor'):
    print('{0}:{1}'.format(el.text,
                           el.getparent().find('tytul').get('lang'), ))


#lxml obsluguje tez bardziej zlozone zapytania
print('tytuly ksiazek zaczynajace sie na A:')
print('\n'.join([x.text for x in  
    xml.xpath('.//*[name()="tytul" and starts-with(text(),"A")]')]))

#lxml obsluguje tez przestrzenie nazw (namespaces)
print('''autorzy o nazwiskach zaczynajacych sie na A, 
niezależnie od przestrzeni nazw:''')
print('\n'.join(['%s %s'%(x.tag,x.text,) for x in  
    xml.xpath('.//*[local-name()="autor" and starts-with(text(),"A")]')]))

print('''ksiazki o autorach zaczynajacych sie na A, 
z przestrzeni nazw {https://wrobelmaciek.blogspot.com/ksiazki}:''')
print('\n'.join(['%s %s'%(x.tag,x.text,) for x in
    xml.xpath('.//g:autor[starts-with(text(),"A")]',
    namespaces={'g':'https://wrobelmaciek.blogspot.com/ksiazki'})]))


#jesli nie potrzebujemy zlozonych zapytan, mozemy uzyc tez findall
#kompatybilnego z xml.ElementTree z biblioteki standardowej pythona
print('autorzy ksiazek i jezyk tytulu - findall')
for el in xml.findall('.//autor'):
    print('{0}:{1}'.format(el.text,
                           el.getparent().find('tytul').get('lang'), ))


#findall obsluguje tylko najprostsze zapytania XPath
try:
    print('to nie zadziala')
    for el in xml.findall('.//*[name()="autor"]'):
        print('{0}:{1}'.format(el.text,
                               el.getparent().find('tytul').get('lang'),
        ))
except SyntaxError:
    print('zbyt skomplikowany predykat')