DOM (Document Object Model)
Existují dva přístupy k práci s XML formátem. Prvnímu se říká DOM (o tom bude tato část) a druhému se říká SAX (o tom bude další kapitola).
DOM vytvoří tzv. Document Object Model. Načte celý dokument do paměti a udělá z něj nějakou strukturu, ve které jsou uložené všechny informace, které dokument obsahuje. Navíc k tomu dostanete funkce, kterými můžete s touto strukturu manipulovat (dotazovat se na hodnoty elementů a atributů, měnit je, přidávat nové, mazat staré atp.).
SAX naproti tomu funguje tak, že jak se čte XML dokument, volají se vámi vytvořené funkce. Když parser XML dokumentu narazí na začátek elementu, na konec elementu, na začátek nebo konec dokumentu, na komentář atp. Vaše funkce dostanou v parametrech všechno důležité, co potřebujete o dané části XML dokumentu vědět. O–vytvoření nějaké struktury (vnitřní reprezentace) se musíte postarat už sami (SAX nic jako DOM nevytváří). Výhodou SAX přístupu je, že se nenačítá celý dokument do paměti, ale čte se „řádek po řádce“ (element po elementu), takže není SAX tak náročný na paměť jako DOM. Hodí se tedy hlavně pro zpracovávání dlouhatánských XML dokumentů.
Knihovna libxml2
Oba tyto přístupy podporuje knihovna libxml2.
Takto knihovna byla původně vytvořena pro projekt Gnome, ale nemá na něm žádné závislosti.
Knihovnu ve svém Linuxu už pravděpodobně budete mít nainstalovanou, ale budete si muset doinstalovat
vývojový balík (jméno balíku záleží na distribuci, kterou používáte, např. v OpenSuSE je to
libxml2-devel
).
Existují i další knihovny pro práci s XML soubory, třeba AsmXml – knihovna napsaná v assembleru :-), eXpat a další. Knihovna libxml2 je ale klasika, proto jsem se rozhodl popsat ji.
Jedno se libxml2 musí nechat. Jeho dokumentace je otřesná. Jedná se hlavně o automaticky vygenerovaný seznam funkcí ze zdrojových kódů a pár příkladů. Pro tuto a další kapitolu jsem připravil několik příkladů, které by vám měli pomoci lépe proniknout do tajů libxml2.
DOM
Teď ale zpět k DOM. Elementům, ale i třeba komentářům a dalším „věcem“,
co se mohou v XML dokumentu oběvit, se říká node
(uzel). Existuje 7 druhů uzlů:
- Dokument (kořenový uzel)
- Element
- Atribut
- Text
- Jmenný prostor (namespace)
- Procesní instrukce
- Komentář
libxml2 používá jako odkaz na uzel datový typ xmlNodePtr
.
DOM, který libxml2 vytvoří, je struktura
xmlDoc.
S tou se pracuje přes odkaz xmlDocPtr
.
A teď k příkladu.
Vytvoření struktury xmlDoc
z XML souboru je velice jednoduché. V zásadě je to jen
zavolání jedné funkce (uvidíte dále). Nejdříve si ale deklaruji funkci
parseConfigDocument()
, ve které načtu informace ze struktury xmlDoc
a uložím si ji do své vlastní struktury Config.
Druhým argumentem funkce je kořenový uzel (což je <sx:config>).
- /*------------------------------------------------*/
- /* 27aDom/dom-parser.h */
- #ifndef _DOM_PARSER
- #define _DOM_PARSER
- #include <libxml/xmlmemory.h>
- #include <libxml/parser.h>
- #include "config.h"
- #endif
- /*------------------------------------------------*/
Funkce main()
nejdřív vytvoří mou strukturu Config
.
Pak zavolá funkci xmlParseFile()
, která
načte XML soubor a (podkud je validní) vrátí odkaz na strukturu xmlDoc
.
Pak získám kořenový element pomocí funkce xmlDocGetRootElement()
a zavolám
svou víše zmíněnou funkci xmlDocGetRootElement()
. Ani nevím, proč jsem
funkci xmlDocGetRootElement()
nezavolal až uvnitř parseConfigDocument()
:-).
- /*------------------------------------------------*/
- /* 27aDom/dom.c */
- #include <stdio.h>
- #include "config.h"
- #include "dom-parser.h"
- {
- xmlDocPtr doc;
- xmlNodePtr cur;
- Config *config = createConfigStructure(2);
- }
- doc = xmlParseFile("config.xml");
- }
- cur = xmlDocGetRootElement(doc);
- xmlFreeDoc(doc);
- }
- parseConfigDocument(doc, cur, config);
- printConfigStructure(config);
- freeConfigStructure(config);
- /* xmlSaveFile("-", doc); // */
- xmlFreeDoc(doc);
- }
- /*------------------------------------------------*/
Funkce parseConfigDocument()
vyplní strukturu Config
.
Tu si pak vytisknu, uvolním ji a ještě uvolním strukturu xmlDoc
(voláním
funkce xmlFreeDoc()
).
Strukturu xmlDoc můžete uložit do souboru funkcí xmlSaveFile()
.
Pokud zadáte jako jméno souboru "-"
, vytiskne se struktura na obrazovku.
Ještě než se dostanu k parseConfigDocument()
,
podívejte se na deklarace pomocných funkcí, které kopírují hodnoty
z xmlNodePtr
. V jejich definicích se neděje nic
zvláštního, jen se zkopíruje text z jednoho místa paměti (z xmlNodePtr)
do jiného (např. do *dest).
- /*------------------------------------------------*/
- /* 27aDom/dom-utils.h */
- #ifndef _DOM_UTILS
- #define _DOM_UTILS
- #include <libxml/parser.h>
- // zkopiruje hodnotu elementu do dest
- // prevede hodnotu elementu na int a ulozi do dest
- //ulozi hodnotu attributu do dest
- #endif
- /*------------------------------------------------*/
Funkce parseConfigDocument()
prochází potomky
kořenového elemetnu (potomky <sx:config>). Když narazí
na element version
nebo databases
,
zavolá funkce, které jsem napsal na parsování
těchto elementů:
- /*------------------------------------------------*/
- /* 27aDom/dom-parser.c */
- #include <stdbool.h>
- #include <string.h>
- #include "dom-parser.h"
- #include "dom-utils.h"
- {
- cur = cur->xmlChildrenNode;
- parseVersion(doc, cur, config);
- }
- parseDatabases(doc, cur, config);
- }
- cur = cur->next;
- }
- }
Není v tom žádná magie. Jen vás může zarazit datový typ xmlChar *
.
V zásadě je to jen alias pro unsigned char *
.
Pozor, libxml2 předpokládá, že váš dokument je v UTF-8! Dokáže si poradit
i s jinými kódováními, ale to už je nad rámec této kapitoly.
Taky si je potřeba uvědomit, že text mezi elementy, i když jde jen o tabulátory, mezery a nové znaky, tvoří textové elementy. Proto nemá element xs:config jen 2 potomky (version a databases), ale má jich 5 (3 textové uzly navíc).
Funkce parseVersion()
prochází potomky elementu <version> a hledá
elementy major a minor. Když je najde, zkopíruje jejich obsah do struktury
Config. Pokud najde oba elementy, vrátí funkce
true, jinak false.
- {
- cur = cur->xmlChildrenNode;
- copyXMLString(doc, cur, config->version.major,
- }
- copyXMLString(doc, cur, config->version.minor,
- }
- cur = cur->next;
- }
- }
Funkce parseDatabases()
jenom hledá mezi svými potomky
elementy database a zavolá funkci parseDatabase()
,
která zkopíruje data pro jednu položku databáze. parseDatabase()
nezískává
hodnoty jen z hodnot elementů, ale i atributů type
a enabled
.
Pokud nalezne všechny položky, vrací true.
- {
- cur = cur->xmlChildrenNode;
- parseDatabase(doc, cur, &(config->databases[i]));
- i++;
- }
- cur = cur->next;
- }
- return i;
- }
- {
- database->type = getDatabaseTypeFromText(tmp);
- }
- }
- cur = cur->xmlChildrenNode;
- copyXMLString(doc, cur, database->name,
- copyXMLString(doc, cur, database->password,
- }
- copyXMLString(doc, cur, database->schema,
- }
- copyXMLString(doc, cur, database->host,
- }
- copyXMLInteger(doc, cur, &(database->port));
- }
- cur = cur->next;
- }
- database->port = getDefaultPort(database->type);
- }
- }
- /*------------------------------------------------*/
Příklad je to dlouhý, ale sami vidíte, že celé kouzlo je jen v procházení struktury xmlNode
pomocí odkazu na děti (cur->xmlChildrenNode
)
pomocí cur->next
a porovnávání
názvu elementu pomocí xmlStrcmp()
.
Struktura xmlNode obsahuje kromě odkazu na děti ->xmlChildrenNode (alias ->children) taky odkazy na parent (rodič), next (další), prev(předchozí) nebo next (předchozí). Díky těmto odkazům můžete brouzdat po struktuře XML dokumentu jak se vám zachce.
Jako poslední jsou tu pomocné funkce pro získávání hodnot z elementů a atributů.
K tomu používají funkce xmlNodeListGetString()
a xmlGetProp()
.
Tyto funkce pro výsledek alokují paměť, takže ji musíte uvolnit pomocí xmlFree()
.
- /*------------------------------------------------*/
- /* 27aDom/dom-utils.c */
- #include <stdbool.h>
- #include <string.h>
- #include "dom-utils.h"
- {
- xmlChar *string;
- string = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
- dest[destLength - 1] = '\0'; // pro jistotu
- xmlFree(string);
- }
- }
- /**
- * Pozn: funkce atoi() nedetekuje chyby.
- * Lepsi by bylo pouzit strtol()
- */
- {
- xmlChar *string;
- string = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
- xmlFree(string);
- }
- }
- {
- xmlChar *value;
- value = xmlGetProp(cur, (xmlChar *) name);
- dest[destLength - 1] = '\0'; // pro jistotu
- xmlFree(value);
- }
- /*------------------------------------------------*/
A to je vše. Snad to ani moc nebolelo :-). I když je příklad opravdu dlouhý,
vlastně jsem vám představil jen minimum z funkcí a vlastnotí
xmlNode
či xmlPtr
.
Nainstalujte si libxml2-devel
. Bez toho tento příklad nepřeložíte.
Překlad může vypadat takto:
Výstup z programu:
Version: 1.0. Database: MySQL; enabled Username: petr Password: ***** Schema: test Host: localhost Port: 3306 Database: PostgreSQL; Username: pavel Password: Schema: petr Host: localhost Port: 5432
Nezmínil jsem se třeba vůbec o tom, jak měnit hodnoty elementů a atributů, přidávat nové atp.
To už si můžete nastudovat z dokumentace. Hledejte funkce
xmlNewNode()
, xmlNewProp()
, xmlNewText()
,
xmlUnlinkNode()
, nebo xmlUnsetProp()
.
Také by se asi slušelo zmínit se o funkci
xmlParseMemory()
,
která, na rozdíl od xmlParseFile()
, neparsuje XML ze souboru ale z paměti
(z nějakého řetězce).
Použité funkce
Aby se vám nepletli funkce které jsem napsal já s funkcemi z libxml2, tak tady máte seznam libxml2 funkcí, které jsem v této kapitole použil.
* Parse an XML file and build a tree.
* Automatic support for ZLIB/Compress compressed document is
* provided by default if found at compile-time.
* @param filename the filename
* @return the resulting document tree if the file was wellformed, NULL otherwise.
*/
xmlDocPtr xmlParseFile (const char * filename);
Dokumentace: xmlParseFile
* Get the root element of the document
* (doc->children is a list containing possibly comments, PIs, etc ...).
* @param doc the doument
* @return the xmlNodePtr for the root or NULL
*/
xmlNodePtr xmlDocGetRootElement (const xmlDoc * doc);
Dokumentace: xmlDocGetRootElement
* Free up all the structures used by a document, tree included.
* @param cur pointer to the document
*/
void xmlFreeDoc (xmlDocPtr cur);
Dokumentace: xmlFreeDoc
Dokumentace: xmlStrcmp
* Build the string equivalent to the text contained in the Node list made of TEXTs and ENTITY_REFs
* @param doc the document
* @param list a Node list
* @param inLine should we replace entity contents or show their external form
* @return a pointer to the string copy, the caller must free it with xmlFree().
*/
xmlChar * xmlNodeListGetString (xmlDocPtr doc, const xmlNode * list, int inLine);
Dokumentace: xmlNodeListGetString
* Search and get the value of an attribute associated to a node
* This does the entity substitution. This function looks in DTD attribute
* declaration for #FIXED or default declaration values unless DTD use has
* been turned off.
* NOTE: this function acts independently of namespaces associated to the
* attribute. Use xmlGetNsProp() or xmlGetNoNsProp() for namespace aware
* processing.
* @param node the node
* @param name the attribute name
* @return the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
*/
xmlChar * xmlGetProp (const xmlNode * node, const xmlChar * name);
Dokumentace: xmlGetProp
Dokumentace: ? (nenašel jsem)
Kromě toho jsem používal atributy struktur:
-
xmlNode (např.
cur->xmlChildrenNode
je alias procur->children
).