Skip to content

14. Przetwarzanie dokumentów XML

  

Rozważmy następujący dokument XML:

<tribu>
  <enseignant>
    <personne sexe="M">
      <nom>dupont</nom>
      <prenom>jean</prenom>
      <age>28</age>
      ceci est un commentaire
    </personne>
    <section>27</section>
  </enseignant>
  <etudiant>
    <personne sexe="F">
      <nom>martin</nom>
      <prenom>charline</prenom>
      <age>22</age>
    </personne>
    <formation>dess IAIE</formation>
  </etudiant>
</tribu>

Analizujemy ten dokument, aby uzyskać następujący wynik na konsoli:

 tribu
  enseignant
   (personne,(sexe,M) )
    nom
     [dupont]
    /nom
    prenom
     [jean]
    /prenom
    age
     [28]
    /age
   /personne
   section
    [27]
   /section
  /enseignant
  etudiant
   (personne,(sexe,F) )
    nom
     [martin]
    /nom
    prenom
     [charline]
    /prenom
    age
     [22]
    /age
   /personne
   formation
    [dess IAIE]
   /formation
  /etudiant
 /tribu

Musimy wiedzieć, jak rozpoznać:

  • tag początkowy, taki jak <kształcenie>;
  • tag końcowy, taki jak </nauczyciel>;
  • tag początkowy z atrybutami, np. <osoba płeć="F">;
  • treść tagu, np. martin w <nom>martin</nom>.

Program służący do analizy kodu XML nazywa się parserem XML. Dwa moduły zapewniają nam funkcje potrzebne do analizy kodu XML: xml.sax i xml.sax.handler.

Moduł [xml.sax] udostępnia nam parser XML za pomocą instrukcji:

xml_parser=xml.sax.make_parser()

Ten parser analizuje tekst XML sekwencyjnie. Wywołuje metody użytkownika w odpowiedzi na zdarzenia:

  • metodę startElement przy rozpoczęciu tagu;
  • metodę endElement przy zakończeniu tagu;
  • metodę characters w treści tagu.

Musimy wskazać parserowi klasę implementującą te metody:

xml_parser.setContentHandler(XmlHandler())

Przekazujemy do metody setContentHandler parsera instancję klasy implementującej metody startElement, endElement, characters. Wykorzystywana klasa jest klasą pochodną klasy xml.sax.handler.ContentHandler. Powyższe metody są wywoływane z następującymi parametrami:

def startElement(self,name,attributs):
  • name to nazwa tagu początkowego. attributs to słownik atrybutów tego tagu. Tak więc dla tagu <personne sexe="M"> otrzymamy name="personne" i attributs={'sexe':'M'}
def endElement(self,name):
  • name to nazwa tagu końcowego. Tak więc dla tagu </etudiant> otrzymamy name='etudiant'.
def characters(self,data):
  • „data” to treść znacznika. Tak więc, jeśli znacznik ma postać
<nom>
    dupont
</nom>

otrzymamy data='\r\n dupont\r\n '. Zazwyczaj usuwa się spacje przed i po danych.

Po tym wyjaśnieniu możemy przejść do skryptu analizującego dokument XML:


Program (xml_sax_01)


# -*- coding=utf-8 -*-

import xml.sax, xml.sax.handler,re

# klasa zarządzania XML
class XmlHandler(xml.sax.handler.ContentHandler):

    # funkcja wywoływana po napotkaniu znacznika początkowego
    def startElement(self,name,attributs):
        global depth
        # sekwencja spacji (wcięcie)
        print " " * depth,
        # atrybuty
        precisions=""
        for (attrib,valeur) in attributs.items():
            precisions+="(%s,%s) " % (attrib,valeur)
        # wyświetlana jest nazwa znacznika i ewentualne atrybuty
        if precisions :
            print "(%s,%s)" % (name,precisions)
        else :
            print name
        # kolejny poziom drzewa
        depth+=1
        # czy jest to tag danych?
        global balisesDonnees,baliseDeDonnees
        if balisesDonnees.has_key(name.lower()):
            baliseDeDonnees=1

    # funkcja wywoływana po napotkaniu znacznika końcowego
    def endElement(self,name):
        # koniec tagu
        # poziom wcięcia
        global depth
        depth-=1
        # sekwencja spacji (wcięcie)
        print " " * depth,
        # nazwa znacznika
        print "/%s" % (name)

    # funkcja wyświetlania danych
    def characters(self,data):
        # dane
        global baliseDeDonnees

        # czy bieżący tag jest tagiem danych?
        if not baliseDeDonnees :
            return
        # poziom wcięcia
        global depth
        # ciąg spacji (wcięcie)
        print " " * depth,
        # dane są wyświetlane
        match=re.match(r"^\s*(.*)\s*$",data)
        if match:
            print "[%s]" % (match.groups()[0])
        # koniec znacznika danych
        baliseDeDonnees=False

# ------------------------------------------- main  
# program
# dane
file="data.xml"       # plik xml
depth=0               # poziom wcięcia = głębokość w drzewie
balisesDonnees={"nom":1,"prenom":1,"age":1,"section":1,"formation":1}
baliseDeDonnees=True     # wartość „true” oznacza, że mamy do czynienia z tagiem danych

# tworzymy obiekt do analizy tekstu XML
xml_parser=xml.sax.make_parser()
# menedżer tagów
xml_parser.setContentHandler(XmlHandler())
# przetwarzanie pliku XML
xml_parser.parse(file)

Uwagi:

  • skrypt korzysta z biblioteki funkcji modułów xml.sax, xml.sax.handler (wiersz 3);
  • wiersz 62: analizowany plik XML;
  • wiersz 68: parser XML;
  • wiersz 70: menedżerem zdarzeń generowanych przez parser będzie instancja klasy XmlHandler;
  • wiersz 72: uruchomiono analizę dokumentu XML;
  • wiersz 6: klasa implementująca metody startElement, endElement, characters. Jest ona pochodną klasy xml.sax.handler.ContentHandler, która implementuje metody wykorzystywane przez parser;
  • wiersz 9: metoda startElement;
  • wiersz 30: metoda endElement;
  • wiersz 41: metoda characters.

Wyniki są takie same, jak te przedstawione na początku tego akapitu.