Az XML formátum napjainkban nagy, és egyre növekvő népszerűségének okait, a használatának előnyeit hosszasan lehetne taglalni, most azonban csak az XML formátum Pythonnal való kezelésének lehetőségeit vizsgáljuk meg.
A Python a "Batteries included" filozófiának megfelelően természetesen
rendelkezik beépített lehetőségekkel az XML dokumentumok kezelésére,
a lehetőségeink azonban ezekkel koránt sem merültek ki. Az XML-SIG*
célja, hogy a nyelvhez olyan eszközöket készítsenek, melyek a
Pythont az elsődleges XML-kezelő nyelvvé emelik.
*
A Python felhasználói és fejlesztői SIG-ekbe (Special Interest Group) tömörülnek,
hogy bizonyos területeken a nyelv fejlődését elősegítsék, illetve gyorsítják.
Ezek egyike az XML-SIG, teljes nevén Special Interest Group for XML Processing in
Python. Ezen kívül jelenleg körülbelül tíz SIG működik többek közt a képfeldolgozás, az
adatbázis-kezelés, illetve a Python webes -kliens és szerver oldali- alkalmazásának
elősegítésért.
Más csoportok már elérték céljukat és befejezték működésüket, de levelezőlistáik
archívuma továbbra is elérhető. Ide tartoznak például a threadekkel, grafikus felülettel,
matematikai számításokkal foglalkozó csoportok.
xmllib
A 2.0-s verzió óta deprecated, vagyis használata nem javasolt!
Ha nem akarjuk reguláris kifejezésekkel nulláról kezdeni az XML-kezelést, akkor ez a
legalacsonyabb szintű megoldás, amit a Python nyújt. Stream alapú megoldásról van
szó, ami azt jelenti, hogy a program folyamatosan kapja meg az XML kód részleteit,
és az egyes XML-tagek megtalálásakor az általunk az adott taghez rendelt függvényt
hajtja végre a program. Egy beépített osztályból való származtatással, illetve a
származtatott osztályban meglévő műveletek felüldefiniálásával és új metódusok
definiálásával hozhatunk létre a saját XML formátumunkhoz illeszkedő parsert.
Nem végez validálást, vagyis nem tudja megállapítani, hogy egy adott DTD szerint
a dokumentum helyes-e.
SAX (Simple API for XML)
Működésében nagyon hasonlít az xmllibhez, de annál kicsit magasabb szinten
való XML-kezelést tesz lehetővé. Szintén stream alapú, eseményvezérelt módszer,
de már tudja ellenőrizni, hogy a dokumentum megfelel-e egy adott DTD-nek, és a
hibakezelésre is ad lehetőséget. A parser fölé egy ún. handlert rendelünk, ez végzi
munka a lényegi (magasabb szintű) részét.
DOM (Document Object Model)
Az XML dokumentum fa-szerkezetű reprezentációját adja, vagyis az előzőekkel ellentétben egyszerre beolvassa az egész dokumentumot a memóriába, ahol a fa-szerkezet tetszőlegesen bejárható és módosítható, majd visszaírható a lemezre. Új XML állományok létrehozására is kényelmesen használható, hiszen csak egy megfelelően definiált osztály egy objektumának adattagjait kell a szükséges adatokkal kitölteni, majd egyetlen metódushívással létrejön az új XML fájl. Eredetileg sem a SAX-ot, sem a DOM-ot nem a Pythonhoz találták ki, mindkettőnek vannak más nyelvű implementációi is.
Ha a feladatunk azt kívánja, hogy egy XML fájlt elejétől a végéig elolvasunk, és közben bizonyos műveleteket hajtsunk végre, mint például egy adatszerkezet felépítése, vagy valamilyen információ összegyűjtése (összeg-, átlagszámítás, stb) akkor a SAX a legmegfelelőbb eszköz. Kisebb változtatások (attribútumok, vagy egy elem tartalmának megváltoztatása) könnyen elvégezhetők vele, de nem célszerű viszont olyankor használni, mikor a dokumentum szerkezetében kell módosításokat végezni, mondjuk elemek egymásba ágyazását megváltoztatni. Például egy könyvet leíró XML dokumentumban a fejezetek sorrendjének megváltoztatásához a DOM a megfelelő eszköz, de ha csak bizonyos típusú elemek tartalmát akarjuk kigyűjteni, akkor a SAX is tökéletes.
A SAX egyik előnye a gyorsaság és egyszerűség. Tegyük fel, hogy van egy adatbázisunk,
egy hozzá tartozó bonyolult DTD-vel, képregények nyilvántartására, és ki akarjuk
keresni Neil Gaiman összes művét. Ehhez a konkrét feladathoz szükségtelen
értelmezni az összes képregény összes adatát, tehát elég egy olyan osztály-példányt
készíteni, amit csak a writer
elemekkel foglalkozik, az összes többit
figyelmen kívül hagyja.
A másik előny, hogy nem kell az egész dokumentumot egyszerre a memóriában tartani. Mivel az XML meglehetősen "terjengős" nyelv, általában az 1MB-nál nagyobb állományok kezelésére a nagyon nagy memóriaigény miatt a DOM és más olyan módszerek használata, melyek az egész dokumentum-szerkezetet a memóriában tárolják nem javasolt.
A SAX használatához négy interfészre van szükség. Mivel ilyen nyelvi elem a Pythonban
nincsen, ezek osztályokként vannak megvalósítva. Metódusaik törzse egyetlen
pass
utasítás így csak azokat a metódusokat kell megírni a
leszármazottaikban, amelyek
ténylegesen csinálnak valamit.
ContentHandler |
Ez az osztály a SAX lelke, ennek metódusai hívódnak meg a feldolgozás elején, végén illetve az egyes elemek feldolgozásának elején és végén. |
---|---|
DTDHandler |
A DTD kezelésével kapcsolatos metódusok összefogója |
EntityResolver |
A külső hivatkozások feloldására szolgál. Ha nincsenek a dokumentumban ilyenek, akkor használható az alapértelmezett üres EntityResolver. |
ErrorHandler |
Nem meglepő módon a parser ennek az osztálynak a metódusait használja hibajelzésre és kezelésre. |
Térjünk vissza az előző példához! Itt van a képregény-gyűjtemény egy része:
Egy XML dokumentumnak pontosan egy gyökéreleme kell, hogy legyen, ez most a
collection
elem. Minden kiadvány számára egy comic
elemet tartalmaz, melynek attribútumai tárolják a kiadvány címét, és számát. A
comic
elemek viszont több más elemet tartalmazhatnak, mint például
writer
, penciler
, felsorolva az adott szám készítőit.
Először készítsünk egy egyszerű osztályt, ami megmondja, hogy egy adott szám szerepel-e a gyűjteményünkben.
A DefaultHandler
leszármazottja mind a négy osztálynak:
ContentHandler, DTDHandler, EntityResolver, ErrorHandler
.
Ezt a megoldást célszerű választani akkor, ha csak egy egyszerű működésű
osztályra van szükségünk. A másik lehetőség, hogy mind a négy osztályból külön
származtatunk egyet-egyet. Egyik megoldás sem szükségszerűen jobb a másiknál,
a választás leginkább csak ízlés kérdése.
A keresés végrehajtásához az osztálynak tudnia kell, hogy mit is keres. Ezt a konstruktorának
adjuk meg paraméterként. Ez az egyszerű keresés csak az attribútumok értékeit vizsgálja,
ezért csak a startElement
metódust kell felüldefiniálnunk.
A startElement()
metódus paraméterként a feldolgozás alatt
álló elem nevét és egy asszociatív tömbben az attribútumait kapja. Ez a metódus a
dokumentum minden XML elemére meghívódik. Ha a függvény elejére beszúrjuk
az alábbi sort:
Ahhoz, hogy ezt ténylegesen használni tudjuk szükség van még egy legfelső szintű kódrészletre, amit létrehozza és összekapcsolja a parser és a FindIssue objektumokat, és a parser egy metódusának meghívásával elindítja az elemzést.
SAXot használva a feladatunk alapvetően a handler létrehozása, a parser már adott.
Ha szükségesnek látjuk, kiválaszthatjuk mi magunk is a használt parsert, de ha nem
akarunk ezzel foglalkozni, akkor a make_parser
osztály
megteszi ezt helyettünk.
Jelenleg is számos XML parser létezik a nyelvhez, és még továbbiak megjelenése várható. Az xmllib.py a Python standard könyvtárának része, így mindig használható, de nem túl gyors. Ennek egy gyorsabb változatra megtalálható az xml.parsers modulban.
Az xml.parsers.expat még gyorsabb, így ha az adott rendszeren hozzáférhető,
jelenleg ez a legjobb választás. Ez a modul C-ben íródott, emiatt lett gyorsabb,
viszont nem minden Python disztribúciónak része (pl. Windowson hozzátartozik,
UNIXokon nem).
A make_parser
osztály kiválasztja nekünk az elérhető parserek közül a
leggyorsabbat, így ezzel nem kell foglalkoznunk, de megadható neki egy lista is,
ebben az esetben a listán szereplők közül választja a leggyorsabbat.
Ha sikeresen létrehoztuk a parsert a setContentHandler(),
setDTDHandler(), setEntityResolver()
, és setErrorHandler()
metódusokkal rendelhetjük hozzá a megfelelő osztályokat.
A fenti program kimenete ez lesz:
Tekintsük a következő XML dokumentumot:
Ebben a &foo;
entitás nem létezik, és a comic
elem nincs lezárva (ha ez egy üres elem lenne, a "/>" karakterekkel kellene
lezárni ">" helyett). Emiatt a feldolgozáskor egy SAXParseException
kivételt kapunk.
Az ErrorHandler
alapértelmezésben kivételt dob minden
hibánál. Ha nem ez az elvárt viselkedés, felül kell definiálnunk a hibakezelő
osztály error()
és fatelError()
metódusait.
Az ErrorHandler warning(), error(), fatalError()
függvényei mind egy
paramétert kapnak, a hibát leíró kivétel-példányt, ami a SAXException leszármazottja.
Ennek az str()
függvényétől kaphatjuk meg a hiba szöveges leírását.
Például, ha nem végzetes kivételek esetén csak figyelmeztetést akarunk adni, és
folytatni az elemzést, a következő kódrészlethez hasonlóra van szükségünk:
Ezután a programunk csak végzetes hibák esetén fog leállni.
A legfelső szintű Document
példány reprezentálja a fa gyökerét.
Ennek egyetlen gyermeke van, a legfelső szintű Element
példány. Ennek
gyermekei reprezentálják a neki megfelelő XML elembe beágyazott további elemeket.
Minden dologhoz, ami XML dokumentumokban előfordulhat rendelkezésünkre
állnak az azokat reprezentáló osztályok: Text, Comment, CDATASection,
EntityReference
stb.
A csúcsokat alkotó osztályok attribútumai, és metódusai teszik lehetővé, hogy
lekérdezzük és megváltoztassuk az adott elem attribútumait, gyermekeket
töröljünk belőle vagy éppen újat adjunk hozzá, illetve visszaalakítsuk XML
formátumba.
Bár a DOM specifikációja nem követeli meg, hogy az egész dokumentumot
leíró fát folyamatosan a memóriában kell tartani, a jelenlegi Python implementáció
így működik, vagyis nagyon nagyméretű dokumentumok módosítására egyelőre nem
alkalmas.
A DOM rengeteg szolgáltatást nyújt, ezekből itt csak a legalapvetőbbeket
tudom bemutatni.
Valójában a Python DOM implementációi nem is valósítják meg az összes szolgáltatást. A DOM-ot a W3C három szintre osztva definiálta, ebből a Python jelenleg az első kettőt valósítja meg, a harmadik szint implementációja most készül. Hogy tovább bonyolódjon a helyzet, a Python két DOM implementációval rendelkezik:
Az xml.dom.ext.reader csomag számos osztályt tartalmaz, amik különböző forrásokból
építik fel a dokumentum-fát. Az xml.dom.Sax2 modul tartalmazza a Reader osztályt,
amint fromStream
metódusa input streamből olvassa az XML kódot.
Az input lehet egy fájl-szerű objektum (bármi, aminek van read() metódusa), vagy egy string.
Utóbbi esetben a stringet URL-ként értelmezi, és az urllib
modult használja a
megnyitásához.
A fromStream()
az XML objektum fájának gyökerét adja vissza.
Az alábbiakban a következő egyszerű XML dokumentumot fogjuk használni
Ezt DOM fává alakítva a következő szerkezetet kaphatjuk
Nem feltétlenül pontosan ezt fogjuk kapni, mivel a különböző parserek esetleg eltérően kezelik a szöveg típusú elemeket, így a fenti fa Text elemeit elképzelhető, hogy több Text elem sorozataként kapjuk meg.
A DOM fa a Print(doc, stream)
, vagy a PrettyPrint(doc, stream)
metódusokkal alakítható vissza XML-be. Ha a stream
-et nem adjuk
meg akkor a függvények a standard outputra írnak. A Print()
változtatás
nélkül, egyszerűen visszaalakítja a fát XML-be, a PrettyPrint()
viszont
kitörölhet és beszúrhat whitespace karaktereket, hogy szépen formázott XML kódot adjon.
Először tekintsük a Node osztály fontosabb attribútumait és metódusait. Az összeses
többi XML elemet reprezentáló osztály (Document, Element,Text
stb) ennek
a leszármazottja. Nagyon sok feladat megoldható pusztán a Node
által
biztosított interfész használatával.
Attribútum | Jelentés |
---|---|
nodeType | Egész konstans. Megadja a fa adott csúcsának típusát, pl. ELEMENT_NODE, TEXT_NODE stb. |
nodeName | A csúcs neve. Bizonyos típusú elemeknél, pl. Element , a név az elem neve, másoknál, pl. Text -nél egy konstans string pl. #text |
nodeValue | A csúcs értéke. Bizonyos típusú elemeknél, pl. Text , az érték egy string az elem szövegszerű tartalmával, másoknálegyszerűen None |
parentNode | A csúcs szülője. A gyökérnél None |
childNodes | A gyermek csúcsok listája (lehet üres is) |
firstChild | A csúcs első gyereke. None ha nincs gyereke |
lastChild | A csúcs utolsó gyereke. None ha nincs gyereke |
previousSibling | A csúcs előző testvére. None ha a csúcsnak nincs szülője, vagy előző testvére |
nextSibling | A csúcs következő testvére. None ha a csúcsnak nincs szülője, vagy következő testvére |
ownerDocument | Arra a dokumentumra mutató referencia, ami az adott csúcsot tartalmazza |
attributes | A NamedNodeMap osztály egy példánya. Lényegében egy asszociatív tömb attribútum név-érték párokkal |
Metódus | Hatás |
---|---|
appenChile(newChild) | Hozzáfűzi a newChild objektumot a gyermekek listájának végéhez. |
removeChild(oldChild) | Kiveszi a fából az oldChild csúcsot, de nem törli. Az oldChild szülője a továbbiakban None lesz. |
replaceChild(newChild, oldChild) | Kicseréli az oldChild csúcsot newChild -ra. Mindkettőnek az adott csúcs gyermekének kell lennie! |
insertBefore(newChild, refChild) | Beszúrja a newChild csúcsot refChild elé. A refChild -nak az adott csúcs gyermekének kell lennie! |
hasChildNodes() | Igaz, ha a csúcsnak vannak gyermekei |
cloneNode(deep) | A csúcs másolatával tér vissza. Ha deep hamis a másolatnak nem lesznek gyermekei. Ha igaz, a művelet a gyermekeket is lemásolja. |
Az Element
és Document
csúcsoknak van még
egy nagyon hasznos metódusa, a getElementsByTagName(tagName)
.
Ez egy listát ad vissza, amiben megtalálható a csúcs
összes adott taggel kezdődő eleme. Például a dokumentum fejezetei megkaphatók a document.getElementsByTagName('chapter')
függvényhívással.
Az egész dokumentum-fa gyökere a Document
csúcs. Ennek a
documentElement
attribútuma az az Element
csúcs, ami a tényleges dokumentumszerkezet gyökérelemét tartalmazza. A Document
csúcsnak lehetnek más gyermekei is, pl. a ProcessingInstruction
csúcsok, de legfeljebb egy
Element
et tartalmazhat.
Ha egy új DOM-fát építünk fel, a Document
csúcs create*()
metódusait használhatjuk, például a createElement()
és createTextNode()
metódusokat.
Ha már megvan a fánk, a másik legalapvetőbb művelet annak bejárása.
A Document
csúcs
metódusát használva létrehozhatunk egy TreeWalker objektumot.
Ennek segítségével járhatjuk be a root
elemben
gyökerező részfát. Az éppen aktuális elem a TreeWalker
currentNode
attribútumában található, a továbblépés pedig a beszédes nevű parentNode(), firstChild(), lastChild(), and nextSibling(), previousSibling()
metódusokkal lehetséges.
A whattoshow
egy bitmaszk, amiben minden elemtípusra megadhatjuk, hogy
bejárás közben az adott típusú elemeket látni szeretnénk-e. A bitmaszk értékeit a NodeFilter
osztály konstansai segítségével állíthatjuk be: a 0 elrejt minden csúcsot,
a NodeFilter.SHOW_ALL
mindent megmutat, a SHOW_ELEMENT
, SHOW_TEXT
és társaik értelemszerűen csak az adott típusú elemeket.
Ezt tovább lehet finomítani: a filter
egy függvény, ami minden bejárt elemre meghívódik, és NodeFilter.FILTER_ACCEPT
, NodeFilter.FILTER_SKIP
vagy
NodeFilter.FILTER_REJECT
értékkel térhet vissza. REJECT esetén a csúcsot és a gyermekeit is kihagyjuk a bejárásból, SKIP esetén csak a csúcsot hagyjuk ki, de a gyermekeit bejárjuk, ACCEPT esetén a csúcsot és gyermekeit is bejárjuk.
Ha filter
helyett None-t adunk meg mindent bejárunk.
A következő példa a teljes dokumentumot bejárja, és minden tag nevét kiírja.
Említésre érdemes még a pixie
modul, mely egy újabb érdekes
megközelítése az XML feldolgozásnak. A DOM-hoz hasonlóan egy
fa-reprezentációját adja az XML dokumentumnak, de ezen kívül van még egy
lényeges funkciója: az XML dokumentumokat át tudja alakítani egy sor-orientált
formátumba (és persze vissza is). A PYX formátum értelme, hogy az XML-nél sokkal
egyszerűbben kezelhető a Python stringeket és reguláris kifejezéseket kezelő
utasításaival, illetve olyan sor-alapú eszközökkel, mint például a UNIX szűrők.