XML (Extensible Markup Language)
XML je rozšiřitelný značkovací jazyk. Pokud o XML slyšíte poprvé, asi to zní hrozně složitě. Ale XML je vlastně jednoduché, XML je cool, XML mají programátoři moc rádi :-). Používá se hlavně pro výměnu dat mezi různými systémy, nebo na uložení configurace atp. Kdo nepoužívá XML, jako by nebyl.
V několika dalších kapitolách popíšu knihovnu libxml2, která poskytuje funkce pro práci s XML soubory. Přemýšlel jsem, jestli vysvětlovat i XML jako takové. Nakonec jsem se rozhodl, že ho jen velmi stručně představím těm, kteří o XML nikdy neslyšeli. Pokusím se popsat naprosté minimum k tomu, aby pochopili, jak XML dokumenty vypadají a k čemu mohou být dobré. Dále se pak budu věnovat už jen knihovně libxml2. Pokud vám můj výklad XML příjde až příliš stručný, podívejte se na internetu po nějakém hutnějším tutoriálu.
XML je formát standardizovaný organizací W3C. To je organizace, která má přes 400 členů. Mezi nejvýznamější patří IBM, Microsoft, Google, Apple, Adobe nebo třeba Sun Microsystem. XML bylo navrženo v roce 1996 jako součást standardu SGML (Standard Generalized Markup Language), který je ale o poznání složitější, takže se tak moc nerozšířil.
Struktura XML
Jednoduchý XML soubor vypadá například takto:
<database id="mysql">
<host>localhost</host>
<name>petr</name>
<password></password>
</database>
</config>
Toto je XML soubor, který jsem si vymyslel, pro uložení konfigurace. To, co se zapisuje do špičatých závorek se jmenuje element. Elementy jsou buď párové, jako například <config></config>, nebo nepárové, jako například <password/>.
Uvnitř párového elementu může být buď text, nebo nějaký další element, tzv. vnořený element. Vnořenému elementu se také říká, že je to dítě nadřazeného elementu. Nadřazenému elementu se pak logicky zase říká rodič.
Elementy mohou mít ještě atributy. V příkladu nahoře má element database
atribut id
,
který má hodnotu mysql.
Atributy a elementy si vymýšlíte! XML nedefinuje jaké máte používat elementy nebo atributy, natož jejich význam. XML popisuje jen pravidla, jak XML dokumen vytvořit, jako například:
- Každý XML dokument musí mít
kořenový element
, tj. element, kterým dokument začíná (a končí) a který obsahuje všechny ostatní elementy. V příkladu nahoře je to elemenetconfig
. - Elementy musí tvořit stromovou strukturu. Nemůžete tedy napsat něco takového: <element1><element2>....</element1></element2>. Element musí být celý uvnitř jiného elementu.
- Element začíná a končí špičatými závorkami. Ukončovací element má po < lomítko / (viz příklad). Pokud je element nepárový, ukončuje se lomítkem /. (Viz <password/>).
- Element může mít více atributů, ale každý musí mít jiné jméno. Každý atribut musí mít hodnotu za rovnítkem uzavřenou v jednoduchých nebo dvojitých uvozovkách.
- Jména elementů a atributů nesmějí obashovat mezeru.
- Defaultní kódování XML dokumentu je UTF-8.
- Atd. atd. atd.
Co je výhodou XML? Předně, jedná se o textový formát, kterému snadno porozumí i člověk, snadno se přenáší mezi různýmy operačnímy systémy (textové soubory, na rozdíl od binárních, nejsou „citlivé“ na to, jestli pracujete na 32 či 64 bitovém počítači atp.).
Téměř každý programovací jazyk má nějakou knihovnu na práci s XML, díky které snadno zjistíte hodnoty atributů a elementů (já budu popisovat knihovnu libxml2). XML se tak hodí na výměnu dat mezi různými systémy napsanými v různých programovacích jazycích.
Je to taky standard, který každý zná, takže když někomu pošlete data v tomto formátu, bude určitě raději, než když mu je pošlete v nějakém formátu, který si sami vymyslíte. Myslím, že jste už na první pohled z příkladu XML nahoře poznali, jaké jsou přihlašovací údaje do databáze. Že je databáze typu mysql, host je localhost, uživatel je petr a heslo je prázdné (no dobře, tohle bylo možná malinko těžší :-)).
To, co jsem doteď popsal, by vám mělo pro vytváření vlastních XML dokumentů stačit. Existuje ale ještě pár dalších věcí, které XML může obsahovat a které zde popíši. Možná už to bude na vás zbytečně složité, ale chtěl jsem v příkladech s knihovnou libxml2 ukázat práci i s těmito složitějšími záležitostmi, proto je tu také zmíním. Klidně ale poznámky o těchto složitějších záležitostech ignorujte. Můžete se k nim vrátit, až zjistíte, že je potřebujete (až se naučíte XML více do hloubky).
První taková věc je tzv prolog. Ten je v XML dokumentu nepovinný, ale pokud ho chcete uvést, musí být na jeho začátku. Vypadá takto:
V prologu je zapsáno něco podobného atributům. Je tam uvedeno číslo standadru XML (1.0) a kódování, v jakém je soubor (UTF-8). Používejte, pokud možno, vždy UTF-8. To je pro XML defaultní kódování a knihovny pro práci s XML si sním rozumějí nejlépe.
Druhou složitostí je tzv DTD. DTD může být v jiném souboru
než je XML (v XML se zapíše jen odkaz na tento DTD soubor), nebo může být
součástí XML. DTD se používá pro popis toho, co XML může obsahovat za elementy a
atributy. Používá se k validaci, tj. ověření, zda XML soubor obsahuje to co
DTD hlásá, nebo ne. DTD v XML začíná <!DOCTYPE
a končí
]> (viz příklad níže). Pokud s XML začínáte, DTD můžete opravdu klidně ignorovat. Validaci
DTD se budu zabývat v další kapitole.
Komentáře V XML začínají <!--
a končí -->
.
To asi není nic složitého :-).
Procesní instrukce začínají <?jmenoInstrukce
a končí
?>
. Procesní instrucke nemají žádný zvláštní význam. Je jen a vašem programu,
jestli něco hodlá s procesní instrukcí dělat, nebo ne.
Složitejší už jsou namespace. Namespace se používají například tehdy,
když chcete do vašeho XML začlenit obsah jiného XML. Jak víte, názvy elementů si vymýšlíte
sami. Jak tedy odlyšit vaše elementy od těch
cizých, pokud se náhodou jmenují stejně? Pomocí namespace. Namespace nejdříve vytvoříte jako atribut nějakého elementu.
Na rozdíl od běžného atributu začíná namespace xmlns:
a zkratkou pro namespace
(v příkladu dole je zkrata sx
). Hodnotou namespace je cokoliv vás napadne, ale běžně
se použíá nějaké URL, abyste zajistili unikátnost namespace. Každý element, který do daného
namespace spadá, pak začíná zkratkou, dvojtečkou a pak svým názvem, např. sx:config
je element config
z namespace sx
.
Pokud uvedete namespace bez zkratky, stane se tzv defaultní namespace.
Tj každý element, který je pod elementem s definovaným defaultním namespacem patří pod toto namespace,
ikdyž není uvozen žádnou zkratkou. V příkladu dole tedy všechny elementy patří do namespace
http://www.sallyx.org/sally/c/linux/xml
, které má ale zároveň i definovanou
zkratku sx
.
S namespace se pojí následující pojmy:
- URI
- Identifikátor jmenného prostoru (http://www.sallyx.org/sally/c/linux/xml)
- QName
- Kvalifikované jméno = jméno s předponou nebo jméno bez předpony
- Jméno s předponou
- předpona : Lokální jméno (sx:config)
- Expanded name
- URI : Lokální jméno (http://www.sallyx.org/sally/c/linux/xml:config)
- Lokální jméno
- jméno bez předpony (config)
Jak už jsem psal, namespace je něco, co používat opravdu nemusíte, ale abych vám ukázal, jak s ním pracuje libxml2, kdybyste to někdy náhodou potřebovali, v ukázkovém XML souboru jsem ho použil:
<!DOCTYPE sx:config [
<!ELEMENT sx:config (version,databases)>
<!ATTLIST sx:config xmlns:sx CDATA #FIXED "http://www.sallyx.org/sally/c/linux/xml">
<!ATTLIST sx:config xmlns CDATA #FIXED "http://www.sallyx.org/sally/c/linux/xml">
<!ELEMENT version (major,minor)>
<!ELEMENT major (#PCDATA)>
<!ELEMENT minor (#PCDATA)>
<!ELEMENT databases (database*)>
<!ELEMENT database (name,password,schema,host,port?)>
<!ATTLIST database type (mysql|postgres|oracle|sqlite) #REQUIRED>
<!ATTLIST database enabled (true|false) "false">
<!ELEMENT name (#PCDATA)>
<!ELEMENT password (#PCDATA|EMPTY)* >
<!ELEMENT schema (#PCDATA)>
<!ELEMENT host (#PCDATA)>
<!ELEMENT port (#PCDATA)>
]>
<!-- testovaci data -->
<sx:config xmlns:sx="http://www.sallyx.org/sally/c/linux/xml"
xmlns="http://www.sallyx.org/sally/c/linux/xml">
<version>
<major>1</major>
<minor>0.0</minor>
</version>
<databases>
<database type="mysql" enabled="true">
<name><![CDATA[petr]]></name>
<password>*****</password>
<schema>test</schema>
<host>localhost</host>
</database>
<database type="postgres">
<name>pavel</name>
<password></password>
<schema>petr</schema>
<host>localhost</host>
</database>
</databases>
</sx:config>
Teď už by vám nemělo být na tomto příkladu nic moc záhadné. DTD, jak už jsem říkal, popisovat nebudu. Na to si najděte jiný tutorál o DTD. Klidně ho ignorujte, XML soubor se bez něj obejde.
Poslední věc, kterou jsem nezmínil, je sekce CDATA
. V XML dokumentu nemůžete
jen tak zapisovat <, > nebo &, protože mají v XML speciální význam. Když
chcete zapsat do elementů nějaký text, který tyto značky obsahuje, máte dvě možnosti.
První je nahradit tyto znaky sekvencemi z násleudjící tabulky:
Znak | Se zapisuje jako … |
---|---|
< | < |
> | > |
& | & |
Druhou možností je uzavřít text mezi <![CDATA[
a ]]>
. Mezi těmito značkami může obsahovat
text cokoliv, kromě sekvence ]]>
. V přikladu obsahuje sekce
CDATA jenom text petr, čili je text obalený sekcí CDATA úplně zbytečně.
Použil jsem to tam jen jako příklad ;-).
Doufám, že jsem vás tolika informacemi takto zhuštěnými moc nevystrašil. Určitě si někdy někde o XML nastudujte více, patří to k základním programátorským dovednostem.
Struktura Config
V příkladech budu pracovat se souborem config.xml, jehož obsah
jste viděli výše. Informace, které obsahuje,
zapíši do struktury Config
, jejíž definici můžete vidět níže.
Hlavní otázkou, kterou budu řešit, je, jak převést XML do této C-čkové struktury.
- /*------------------------------------------------*/
- /* 27aDom/config.h */
- #ifndef _CONFIG_H
- #define _CONFIG_H
- #include <stdlib.h>
- #include <stdbool.h>
- #include <string.h>
- } Version;
- enum databaseType {
- unknown = 0, mysql, postgres, oracle, sqlite
- };
- enum databaseType type;
- bool enabled;
- int port;
- } Database;
- Version version;
- Database *databases;
- size_t databasesSize;
- } Config;
- #endif
- /*------------------------------------------------*/
K této struktuře jsem vytvořil ještě několik pomocných funkcí. Neobsahují žádné konstrukce, které byste už neznali:
- /*------------------------------------------------*/
- /* 27aDom/config.c */
- #include <stdlib.h>
- #include <stdio.h>
- #include "config.h"
- /**
- * Vytvoří strukturu Config. Všechny položky
- * jsou inicializovány na 0,NULL,false nebo prázdné řetězce.
- * @param size_t databases Počet, pro kolik databází se alokuje paměť
- * @return Config Vrací NULL, pokud se nepodaří alokovat paměť
- */
- {
- Config *config;
- return NULL;
- }
- return NULL;
- }
- config->databasesSize = databases;
- return config;
- }
- /**
- * Uvolní paměť alokovanou pro config
- * @param Config * config
- */
- {
- }
- {
- case mysql:
- case postgres:
- case oracle:
- case sqlite:
- }
- }
- /**
- * @param char * Textový název databáze dle enum typu
- * @return enum DatabaseType
- */
- {
- return mysql;
- return postgres;
- return oracle;
- return sqlite;
- return unknown;
- }
- /**
- * Pomocná funkce pro funkci printconfigStructure()
- * @param enum DatabaseType type
- * @return char * Textový název databáze dle enum typu
- */
- {
- case mysql:
- case postgres:
- case oracle:
- case sqlite:
- }
- }
- /**
- * Vytiskne strukturu Config
- * @param Config * config
- */
- {
- size_t i;
- Database *d;
- config->version.minor);
- d = &config->databases[i];
- getDatabaseTypeAsText(d->type),
- d->enabled ? "enabled" : "");
- "\tHost: %s\n" "\tPort: %i\n", d->name, d->password,
- d->schema, d->host, d->port);
- }
- }
- /*------------------------------------------------*/
Tyto funkce a strukturu Config budu využívat v dalších kapitolách.
Strukturu Config
budu používat jako tzv. vnitřní reprezentaci konfigurace.
Program by měl vždy pracovat s nějakou vnitřní reprezentací, ne přímo
s DOM objektem XML (o něm bude řeč hned v další kapitole),
protože se jednoho dne můžete rozhodnout ukládat konfiguraci
jinak, třeba do databáze. Pokud budete v celém programu pracovat jen
se strukturou Config
a ne s DOM objektem, jediné, co v takovém případě
budete muset změnit, budou funkce pro načtení a uložení konfigurace.
Pracovat v programu s daty nezávisle na způsobu, jak jsou uložená (v XML, databázi,
you name it …), vždycky zdrojové kódy zpřehledňuje a usnadňuje
případné změny.