Przejdź do zawartości

Zanurkuj w Pythonie/Przetwarzanie XML-a

Z Wikibooks, biblioteki wolnych podręczników.

Nurkujemy

[edytuj]

Kolejne dwa rozdziały są na temat przetwarzania XML-a w Pythonie. Będzie to przydatne, jeśli już wiesz, jak wyglądają dokumenty XML, a które są wykonane ze strukturalnych znaczników określających hierarchię elementów itp. Jeśli nic z tego nie rozumiesz, możesz przeczytać coś na ten temat na Wikipedii.

Nawet jeśli nie interesuje Ciebie temat XML-a i tak dobrze by było przeczytać te rozdziały, ponieważ omawiają one wiele ważnych tematów jak pakiety, argumenty linii poleceń, a także jak wykorzystywać getattr jako pośrednik metod.

Bycie magistrem filozofii nie jest wymagane, chociaż jeśli kiedyś spotkaliśmy się z tekstami napisanymi przez Immanuel Kanta, lepiej zrozumiemy przykładowy program.

Mamy dwa sposoby pracy z XML-em. Jeden jest nazywany SAX (Simple API for XML), który działa w ten sposób, że czyta przez chwilę dokument XML i wywołuje dla każdego odnalezionego elementu odpowiednie metody. (Jeśli przeczytaliśmy rozdział 8, powinno to wyglądać znajomo, ponieważ w taki sposób pracuje moduł sgmllib.) Inny jest nazywany DOM (Document Object Model), a pracuje w ten sposób, że jednorazowo czyta cały dokument XML i tworzy wewnętrzną reprezentację, wykorzystując klasy Pythona powiązane w strukturę drzewa. Python posiada standardowe moduły do obydwu sposobów parsowania, ale rozdział ten opisze tylko, jak wykorzystywać DOM.

Poniżej znajduje się kompletny program Pythona, który generuje pseudolosowe wyjście oparte na gramatyce bezkontekstowej zdefiniowanej w formacie XML. Nie przejmujmy się, jeśli nie zrozumieliśmy, co to znaczy. Będziemy głębiej badać zarówno wejście programu, jak i jego wyjście w tym i następnym rozdziale.

Przykład. kgp.py
u"""Generator Kanta dla Pythona Generuje pseudofilozofię opartą na gramatyce bezkontekstowej Użycie: python kgp.py [options] [source] Opcje:  -g ..., --grammar=... używa określonego pliku gramatyki lub adres URL  -h, --help wyświetla ten komunikat pomocy  -d wyświetla informacje debugowania podczas parsowania Przykłady:  kgp.py generuje kilka akapitów z filozofią Kanta  kgp.py -g husserl.xml generuje kilka akapitów z filozofią Husserla  kpg.py "<xref id='paragraph'/>" generuje akapit Kanta  kgp.py template.xml czyta template.xml, aby określić, co ma generować  """ from xml.dom import minidom import random import toolbox import sys import getopt _debug = 0 class NoSourceError(Exception): pass class KantGenerator(object):  u"""generuje pseudofilozofię opartą na gramatyce bezkontekstowej""" def __init__(self, grammar, source=None): self.loadGrammar(grammar) self.loadSource(source and source or self.getDefaultSource()) self.refresh() def _load(self, source):  u"""wczytuje XML-owe źródło wejścia, zwraca sparsowany dokument XML  - adres URL z plikiem XML ("http://diveintopython.org/kant.xml")  - nazwę lokalnego pliku XML ("~/diveintopython/common/py/kant.xml")  - standardowe wejście ("-")  - bieżący dokument XML w postaci łańcucha znaków  """ sock = toolbox.openAnything(source) xmldoc = minidom.parse(sock).documentElement sock.close() return xmldoc def loadGrammar(self, grammar):  u"""wczytuje gramatykę bezkontekstową""" self.grammar = self._load(grammar) self.refs = {} for ref in self.grammar.getElementsByTagName("ref"): self.refs[ref.attributes["id"].value] = ref def loadSource(self, source):  u"""wczytuje źródło source""" self.source = self._load(source) def getDefaultSource(self):  u"""zgaduje domyślne źródło bieżącej gramatyki    Domyślnym źródłem będzie jeden z <ref>-ów, do którego nic się  nie odwołuje. Może brzmi to skomplikowanie, ale tak naprawdę nie jest.  Przykład: Domyślnym źródłem dla kant.xml jest  "<ref id='section'/>", ponieważ 'section' jest jednym <ref>-em, który  nie jest nigdzie <xref>-em w gramatyce.  W wielu gramatykach, domyślne źródło będzie tworzyło  najdłuższe (i najbardziej interesujące) wyjście.  """ xrefs = {} for xref in self.grammar.getElementsByTagName("xref"): xrefs[xref.attributes["id"].value] = 1 xrefs = xrefs.keys() standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs] if not standaloneXrefs: raise NoSourceError, "can't guess source, and no source specified" return '<xref id="%s"/>' % random.choice(standaloneXrefs) def reset(self):  u"""resetuje parser""" self.pieces = [] self.capitalizeNextWord = 0 def refresh(self):  u"""resetuje bufor wyjściowy, ponownie parsuje cały plik źródłowy i zwraca wyjście    Ponieważ parsowanie dosyć dużo korzysta z przypadkowości, jest to  łatwy sposób, aby otrzymać nowe wyjście bez potrzeby ponownego wczytywania  pliku gramatyki.  """ self.reset() self.parse(self.source) return self.output() def output(self):  u"""wyjściowy, wygenerowany tekst""" return "".join(self.pieces) def randomChildElement(self, node):  u"""wybiera przypadkowy potomek węzła    Jest to użyteczna funkcja wykorzystywana w do_xref i do_choice.  """ choices = [e for e in node.childNodes if e.nodeType == e.ELEMENT_NODE] chosen = random.choice(choices) if _debug: sys.stderr.write('%s available choices: %s\n' % \ (len(choices), [e.toxml() for e in choices])) sys.stderr.write('Chosen: %s\n' % chosen.toxml()) return chosen def parse(self, node):  u"""parsuje pojedynczy węzeł XML    Parsowany dokument XML (from minidom.parse) jest drzewem węzłów  złożonym z różnych typów. Każdy węzeł reprezentuje instancję  odpowiadającej jej klasy Pythona (Element dla znacznika, Text   dla danych tekstowych, Document dla dokumentu). Poniższe wyrażenie  konstruuje nazwę klasy opartej na typie węzła, który parsujemy  ("parse_Element" dla węzła o typie Element,  "parse_Text" dla węzła o typie Text itp.), a następnie wywołuje te metody.  """ parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) parseMethod(node) def parse_Document(self, node):  u"""parsuje węzeł dokumentu    Węzeł dokument sam w sobie nie jest interesujący (przynajmniej dla nas), ale  jego jedyne dziecko, node.documentElement jest głównym węzłem gramatyki.  """ self.parse(node.documentElement) def parse_Text(self, node):  u"""parsuje węzeł tekstowy    Tekst węzła tekstowego jest zazwyczaj dodawany bez zmiany do wyjściowego bufora.   Jedynym wyjątkiem jest to, że <p class='sentence'> ustawia flagę, aby  pierwsza litera następnego słowa była wielka. Jeśli ta flaga jest ustawiona,  pierwszą literę tekstu robimy wielką i resetujemy tę flagę.  """ text = node.data if self.capitalizeNextWord: self.pieces.append(text[0].upper()) self.pieces.append(text[1:]) self.capitalizeNextWord = 0 else: self.pieces.append(text) def parse_Element(self, node):  u"""parsuje element    XML-owy element odpowiada bieżącemu znacznikowi źródła:  <xref id='...'>, <p chance='...'>, <choice> itp.  Każdy typ elementu jest obsługiwany za pomocą odpowiedniej, własnej metody.   Podobnie jak to robiliśmy w parse(), konstruujemy nazwę metody  opartej na nazwie elementu ("do_xref" dla znacznika <xref> itp.), a potem  wywołujemy tę metodę.  """ handlerMethod = getattr(self, "do_%s" % node.tagName) handlerMethod(node) def parse_Comment(self, node):  u"""parsuje komentarz    Gramatyka może zawierać komentarze XML, ale my je pominiemy  """ pass def do_xref(self, node):  u"""obsługuje znacznik <xref id='...'>    Znacznik <xref id='...'> jest odwołaniem do znacznika <ref id='...'>.  Znacznik <xref id='sentence'/> powoduje to, że zostaje wybrany w przypadkowy sposób  potomek znacznika <ref id='sentence'>.  """ id = node.attributes["id"].value self.parse(self.randomChildElement(self.refs[id])) def do_p(self, node):  u"""obsługuje znacznik <p>    Znacznik <p> jest jądrem gramatyki. Może zawierać niemal  wszystko: tekst w dowolnej formie, znaczniki <choice>, znaczniki <xref>, a nawet  inne znaczniki <p>. Jeśli atrybut "class='sentence'" zostanie znaleziony, flaga  zostaje ustawiona i następne słowo będzie zapisane dużą literą. Jeśli zostanie  znaleziony atrybut "chance='X'", to mamy X% szansy, że znacznik zostanie wykorzystany  (i mamy (100-X)% szansy, że zostanie całkowicie pominięty)  """ keys = node.attributes.keys() if "class" in keys: if node.attributes["class"].value == "sentence": self.capitalizeNextWord = 1 if "chance" in keys: chance = int(node.attributes["chance"].value) doit = (chance > random.randrange(100)) else: doit = 1 if doit: for child in node.childNodes: self.parse(child) def do_choice(self, node):  u"""obsługuje znacznik <choice>    Znacznik <choice> zawiera jeden lub więcej znaczników <p>. Jeden znacznik <p>  zostaje wybrany przypadkowo i jest następnie wykorzystywany do generowania  tekstu wyjściowego.  """ self.parse(self.randomChildElement(node)) def usage(): print __doc__ def main(argv): grammar = "kant.xml" try: opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) except getopt.GetoptError: usage() sys.exit(2) for opt, arg in opts: if opt in ("-h", "--help"): usage() sys.exit() elif opt == '-d': global _debug _debug = 1 elif opt in ("-g", "--grammar"): grammar = arg source = "".join(args) k = KantGenerator(grammar, source) print k.output() if __name__ == "__main__": main(sys.argv[1:]) 
Przykład. toolbox.py
u"""Różnorodne użyteczne funkcje""" def openAnything(source):  u"""URI, nazwa pliku lub łańcuch znaków --> strumień  Funkcja ta pozwala zdefiniować parser, który przyjmuje dowolne źródło wejścia  (URL, ścieżkę do lokalnego pliku lub znajdującego się gdzieś w sieci,   czy też bieżące dane w postaci łańcucha znaków)  i traktuje je w odpowiedni sposób. Zwracany obiekt będzie zawierał  wszystkie podstawowe metody odczytu (read, readline, readlines).  Kiedy już obiekt nie będzie potrzebny, należy go   zamknąć za pomocą metody .close().    Przykłady:  >>> from xml.dom import minidom  >>> sock = openAnything("http://localhost/kant.xml")  >>> doc = minidom.parse(sock)  >>> sock.close()  >>> sock = openAnything("c:\\inetpub\\wwwroot\\kant.xml")  >>> doc = minidom.parse(sock)  >>> sock.close()  >>> sock = openAnything("<ref id='conjunction'><text>and</text><text>or</text></ref>")  >>> doc = minidom.parse(sock)  >>> sock.close()  """ if hasattr(source, "read"): return source if source == "-": import sys return sys.stdin # próbuje otworzyć za pomocą modułu urllib (gdy source jest plikiem dostępnym z http, # ftp lub URL-a) import urllib try: return urllib.urlopen(source) except (IOError, OSError): pass # próbuje otworzyć za pomocą wbudowanej funkcji open (jeśli source jest ścieżką # do lokalnego pliku) try: return open(source) except (IOError, OSError): pass # traktuje source jako łańcuch znaków import StringIO return StringIO.StringIO(str(source)) 

Uruchom sam program kgp.py, który będzie parsował domyślną, opartą na XML gramatykę w kant.xml, a następnie wypisze kilka filozoficznych akapitów w stylu Immanuela Kanta.

Przykład. Przykładowe wyjście kgp.py
[you@localhost kgp]$ python kgp.py  As is shown in the writings of Hume, our a priori concepts, in reference to ends, abstract from all content of knowledge; in the study of space, the discipline of human reason, in accordance with the principles of philosophy, is the clue to the discovery of the Transcendental Deduction. The transcendental aesthetic, in all theoretical sciences, occupies part of the sphere of human reason concerning the existence of our ideas in general; still, the never-ending regress in the series of empirical conditions constitutes the whole content for the transcendental unity of apperception. What we have alone been able to show is that, even as this relates to the architectonic of human reason, the Ideal may not contradict itself, but it is still possible that it may be in contradictions with the employment of the pure employment of our hypothetical judgements, but natural causes (and I assert that this is the case) prove the validity of the discipline of pure reason. As we have already seen, time (and it is obvious that this is true) proves the validity of time, and the architectonic of human reason, in the full sense of these terms, abstracts from all content of knowledge. I assert, in the case of the discipline of practical reason, that the Antinomies are just as necessary as natural causes, since knowledge of the phenomena is a posteriori. The discipline of human reason, as I have elsewhere shown, is by its very nature contradictory, but our ideas exclude the possibility of the Antinomies. We can deduce that, on the contrary, the pure employment of philosophy, on the contrary, is by its very nature contradictory, but our sense perceptions are a representation of, in the case of space, metaphysics. The thing in itself is a representation of philosophy. Applied logic is the clue to the discovery of natural causes. However, what we have alone been able to show is that our ideas, in other words, should only be used as a canon for the Ideal, because of our necessary ignorance of the conditions. 

[...ciach...]

Jest to oczywiście kompletny bełkot. No dobra, nie całkowity bełkot. Jest składniowo i gramatycznie poprawny (chociaż bardzo wielomówny). Niektóre fragmenty mogą być rzeczywiście prawdą (lub przy najmniej z niektórymi Kant by się zgodził), a niektóre są ewidentnie nieprawdziwe, a wiele fragmentów jest po prostu niespójnych. Lecz wszystko jest w stylu Immanuela Kanta.

Interesującą rzeczą w tym programie jest to, że nie ma tu nic, co określa Kanta. Cała zawartość poprzedniego przykładu pochodzi z pliku gramatyki, kant.xml. Jeśli każemy programowi wykorzystać inny plik gramatyki (który możemy określić z linii poleceń), wyjście będzie kompletnie różne.

Przykład. Proste wyjście kgp.py
[you@localhost kgp]$ python kgp.py -g binary.xml 00101001 [you@localhost kgp]$ python kgp.py -g binary.xml 

10110100