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:

<config>
    <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:

Xml Tree

XML dokument jako stromová struktura

  • 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 elemenet config.
  • 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:

<?xml version="1.0" encoding="UTF-8" ?>

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:

<?xml version="1.0" encoding="UTF-8" ?>

<!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>
XML procesory (tj programy a knihovny, co parsují XML soubory), normalizují nové řádky tak, že je vždy převádějí na LF.

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:

ZnakSe zapisuje jako …
<&lt;
>&gt;
&&amp;

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.

  1. /*------------------------------------------------*/
  2. /* 27aDom/config.h                                */
  3.  
  4. #ifndef _CONFIG_H
  5. #define _CONFIG_H
  6. #include <stdlib.h>
  7. #include <stdbool.h>
  8. #include <string.h>
  9.  
  10.     char major[3];
  11.     char minor[5];
  12. } Version;
  13.  
  14. enum databaseType {
  15.     unknown = 0, mysql, postgres, oracle, sqlite
  16. };
  17.  
  18.     enum databaseType type;
  19.     bool enabled;
  20.     char name[32];
  21.     char password[32];
  22.     char schema[32];
  23.     char host[32];
  24.     int port;
  25. } Database;
  26.  
  27.     Version version;
  28.     Database *databases;
  29.     size_t databasesSize;
  30. } Config;
  31.  
  32. enum databaseType getDatabaseTypeFromText(char *type);
  33. int getDefaultPort(enum databaseType type);
  34. Config *createConfigStructure(size_t databases);
  35. void freeConfigStructure(Config * config);
  36. void printConfigStructure(Config * config);
  37. #endif
  38.  
  39. /*------------------------------------------------*/

K této struktuře jsem vytvořil ještě několik pomocných funkcí. Neobsahují žádné konstrukce, které byste už neznali:

  1. /*------------------------------------------------*/
  2. /* 27aDom/config.c                                */
  3.  
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include "config.h"
  7.  
  8. /**
  9. * Vytvoří strukturu Config. Všechny položky
  10. * jsou inicializovány na 0,NULL,false nebo prázdné řetězce.
  11. * @param size_t databases Počet, pro kolik databází se alokuje paměť
  12. * @return Config Vrací NULL, pokud se nepodaří alokovat paměť
  13. */
  14. Config *createConfigStructure(size_t databases)
  15. {
  16.     Config *config;
  17.     config = (Config *) calloc(1, sizeof(Config));
  18.     if (!config) {
  19.         return NULL;
  20.     }
  21.     config->databases = (Database *) calloc(databases, sizeof(Database));
  22.     if (!config->databases) {
  23.         free(config);
  24.         return NULL;
  25.     }
  26.     config->databasesSize = databases;
  27.     return config;
  28. }
  29.  
  30. /**
  31. * Uvolní paměť alokovanou pro config
  32. * @param Config * config
  33. */
  34. void freeConfigStructure(Config * config)
  35. {
  36.     if (!config)
  37.         return;
  38.     free(config->databases);
  39.     free(config);
  40. }
  41.  
  42. int getDefaultPort(enum databaseType type)
  43. {
  44.     switch (type) {
  45.     case mysql:
  46.         return 3306;
  47.     case postgres:
  48.         return 5432;
  49.     case oracle:
  50.         return 1521;
  51.     case sqlite:
  52.         return 0;
  53.     default:
  54.         return 0;
  55.     }
  56. }
  57.  
  58. /**
  59. * @param char * Textový název databáze dle enum typu
  60. * @return enum DatabaseType
  61. */
  62. enum databaseType getDatabaseTypeFromText(char *type)
  63. {
  64.     if (!strcmp(type, "mysql"))
  65.         return mysql;
  66.     if (!strcmp(type, "postgres"))
  67.         return postgres;
  68.     if (!strcmp(type, "oracle"))
  69.         return oracle;
  70.     if (!strcmp(type, "sqlite"))
  71.         return sqlite;
  72.     return unknown;
  73. }
  74.  
  75. /**
  76. * Pomocná funkce pro funkci printconfigStructure()
  77. * @param enum DatabaseType type
  78. * @return char * Textový název databáze dle enum typu
  79. */
  80. static char *getDatabaseTypeAsText(enum databaseType type)
  81. {
  82.     switch (type) {
  83.     case mysql:
  84.         return "MySQL";
  85.     case postgres:
  86.         return "PostgreSQL";
  87.     case oracle:
  88.         return "Oracle";
  89.     case sqlite:
  90.         return "SQLite 2";
  91.     default:
  92.         return "unknown";
  93.     }
  94. }
  95.  
  96. /**
  97. * Vytiskne strukturu Config
  98. * @param Config * config
  99. */
  100. void printConfigStructure(Config * config)
  101. {
  102.     size_t i;
  103.     Database *d;
  104.     printf("Version: %s.%s\n", config->version.major,
  105.            config->version.minor);
  106.     for (i = 0; i < config->databasesSize; i++) {
  107.         d = &amp;config->databases[i];
  108.         printf("Database: %s; %s\n",
  109.                getDatabaseTypeAsText(d->type),
  110.                d->enabled ? "enabled" : "");
  111.         printf("\tUsername: %s\n" "\tPassword: %s\n" "\tSchema: %s\n"
  112.                "\tHost: %s\n" "\tPort: %i\n", d->name, d->password,
  113.                d->schema, d->host, d->port);
  114.         printf("\n");
  115.     }
  116. }
  117.  
  118. /*------------------------------------------------*/

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.

Komentář Hlášení chyby
Created: 6.9.2014
Last updated: 10.10.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..