Zanurkuj w Pythonie/Parsowanie XML-a

Parsowanie XML-a

edytuj

Jak już mówiliśmy, parsowanie XML-a właściwie jest bardzo proste: jedna linijka kodu. Co z tym zrobimy dalej, to już zależy wyłącznie od nas samych.

Przykład. Ładowanie dokumentu XML (tym razem naprawdę)
>>> from xml.dom import minidom                                           #(1)
>>> xmldoc = minidom.parse('~/zanurkuj_w_pythonie/py/kgp/binary.xml')     #(2)
>>> xmldoc                                                                #(3)
<xml.dom.minidom.Document instance at 010BE87C>
>>> print xmldoc.toxml()                                                  #(4)
<?xml version="1.0" ?>
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
  1. Jak już widzieliśmy w poprzednim podrozdziale, ta instrukcja importuje moduł minidom z pakietu xml.dom.
  2. Tutaj jest ta jedna linia kodu, która wykonuje całą robotę: minidom.parse pobiera jeden argument i zwraca sparsowaną reprezentację dokumentu XML. Argumentem może być wiele rzeczy; w tym wypadku jest to po prostu nazwa pliku dokumentu XML na lokalnym dysku. (Aby kontynuować musimy zmienić ścieżkę tak, aby wskazywała na katalog, w którym przechowujemy pobrane z sieci przykłady.) Możemy także jako parametr przekazać obiekt pliku lub nawet obiekt plikopodobny (ang. file-like object). Skorzystamy z tej elastyczności później w tym rozdziale.
  3. Obiektem zwróconym przez minidom.parse jest obiekt Document, który jest klasą pochodną klasy Node. Ten obiekt Document jest korzeniem złożonej struktury drzewiastej połączonych ze sobą obiektów Pythona, która w pełni reprezentuje dokument XML przekazany funkcji minidom.parse.
  4. toxml jest metodą klasy Node (a zatem jest też dostępna w obiekcie Document otrzymanym z minidom.parse). toxml wypisuje XML reprezentowany przez dany obiekt Node. Dla węzła, którym jest obiekt Document, wypisuje ona cały dokument XML.

Skoro już mamy dokument XML w pamięci, możemy zacząć po nim wędrować.

Przykład. Pobieranie węzłów potomnych
>>> xmldoc.childNodes        #(1)
[<DOM Element: grammar at 17538908>]
>>> xmldoc.childNodes[0]     #(2)
<DOM Element: grammar at 17538908>
>>> xmldoc.firstChild        #(3)
<DOM Element: grammar at 17538908>
  1. Każdy węzeł posiada atrybut childNodes, który jest listą obiektów Node. Obiekt Document zawsze ma tylko jeden węzeł potomny, element główny (korzeń) dokumentu XML (w tym przypadku element grammar).
  2. Aby dostać się do pierwszego (i w tym wypadku jedynego) węzła potomnego, używamy po prostu zwykłej składni do obsługi list. Pamiętajmy, tu nie dzieje się nic nadzwyczajnego; to jest po prostu zwykła lista Pythona zwykłych pythonowych obiektów.
  3. Ponieważ pobieranie pierwszego węzła potomnego danego węzła jest bardzo użyteczną i częstą czynnością, klasa Node posiada atrybut firstChild, który jest synonimem dla childNodes[0]. (Jest też atrybut lastChild, który jest synonimem dla childNodes[-1].)
Przykład. Metoda toxml działa w każdym węźle
>>> grammarNode = xmldoc.firstChild
>>> print grammarNode.toxml()       #(1)
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
  1. Ponieważ metoda toxml jest zdefiniowana w klasie Node, jest ona dostępna w każdym węźle XML-a, nie tylko w elemencie Document.
Przykład. Węzłami potomnymi może być także tekst
>>> grammarNode.childNodes                  #(1)
[<DOM Text node "\n">, <DOM Element: ref at 17533332>, \
<DOM Text node "\n">, <DOM Element: ref at 17549660>, <DOM Text node "\n">]
>>> print grammarNode.firstChild.toxml()    #(2)



>>> print grammarNode.childNodes[1].toxml() #(3)
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
>>> print grammarNode.childNodes[3].toxml() #(4)
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
>>> print grammarNode.lastChild.toxml()     #(5)
  1. Patrząc na XML w kgp/binary.xml, moglibyśmy pomyśleć, że węzeł grammar ma tylko dwa węzły potomne, czyli dwa elementy ref. Ale chyba o czymś zapominamy: o znakach końca linii! Za elementem '<grammar>' i przed pierwszym '<ref>' jest znak końca linii i zalicza się on do węzłów potomnych elementu grammar. Podobnie jest też znak końca linii po każdym '</ref>'; to także zalicza się do węzłów potomnych. Tak więc grammar.childNodes jest właściwie listą 5 obiektów: 3 obiekty Text i 2 obiekty Element.
  2. Pierwszym potomkiem jest obiekt Text reprezentujący znak końca linii za znacznikiem '<grammar>' i przed pierwszym '<ref>'.
  3. Drugim potomkiem jest obiekt Element reprezentujący pierwszy element ref.
  4. Czwartym potomkiem jest obiekt Element reprezentujący drugi element ref.
  5. Ostatnim potomkiem jest obiekt Text reprezentujący znak końca linii za znacznikiem końcowym '</ref>' i przed znacznikiem końcowym '</grammar>'.
Przykład. Drążenie aż do tekstu
>>> grammarNode
<DOM Element: grammar at 19167148>
>>> refNode = grammarNode.childNodes[1] #(1)
>>> refNode
<DOM Element: ref at 17987740>
>>> refNode.childNodes                  #(2)
[<DOM Text node "\n">, <DOM Text node "  ">, <DOM Element: p at 19315844>, \
<DOM Text node "\n">, <DOM Text node "  ">, \
<DOM Element: p at 19462036>, <DOM Text node "\n">]
>>> pNode = refNode.childNodes[2]
>>> pNode
<DOM Element: p at 19315844>
>>> print pNode.toxml()                 #(3)
<p>0</p>
>>> pNode.firstChild                    #(4)
<DOM Text node "0">
>>> pNode.firstChild.data               #(5)
u'0'
  1. Jak już widzieliśmy w poprzednim przykładzie, pierwszym elementem ref jest grammarNode.childNodes[1], ponieważ childNodes[0] jest węzłem typu Text dla znaku końca linii.
  2. Element ref posiada swój zbiór węzłów potomnych, jeden dla znaku końca linii, oddzielny dla znaków spacji, jeden dla elementu p i tak dalej.
  3. Możesz użyć metody toxml nawet tutaj, głęboko wewnątrz dokumentu.
  4. Element p ma tylko jeden węzeł potomny (nie możemy tego zobaczyć na tym przykładzie, ale spójrzmy na pNode.childNodes jeśli nie wierzymy) i jest nim obiekt Text dla pojednyczego znaku '0'.
  5. Atrybut .data węzła Text zawiera rzeczywisty napis, jaki ten tekstowy węzeł reprezentuje. Zauważmy, że wszystkie dane tekstowe przechowywane są w unikodzie.