2. Přehled podpory XML v PHP5 ................................................ 43 2.1 SimpleXML – jednoduše na věc ...................................................... 46 2.2 SAX – čteme pěkně popořádku ...................................................... 49 2.3 DOM – načteme to do paměti ........................................................ 55 2.4 XPath – rychle to najdeme ........................................................... 58
Obsah
7
8
PHP a XML
2.5 XSLT – jazyk budoucnosti ............................................................. 60 2.6 XMLReader – když se zamotáme do SAX ........................................ 62 2.7 Webové služby .......................................................................... 64 2.8 Závěr ....................................................................................... 66
6.7 Zpracování HTML ...................................................................... 157 6.8 Rozšiřování objektů DOM ........................................................... 160 6.9 Další možnosti DOM .................................................................. 161
8.11 Využití XPathu v DOM ............................................................... 190 8.12 Podpora XPath v dalších rozhraních .............................................. 194
10. Validace ........................................................................... 265 10.1 Validace pomocí rozhraní DOM .................................................... 266 10.2 Validace pomocí rozhraní SimpleXML ............................................ 268 10.3 Validace pomocí rozhraní XMLReader ............................................ 268 10.4 Schematron ............................................................................. 272 10.5 Praktické využití validace ........................................................... 273
11. XSLT ................................................................................ 277 11.1 Základy XSLT ........................................................................... 277 11.2 Cykly – iterativní zpracování ....................................................... 282 11.3 Řazení dat .............................................................................. 285 11.4 Podmíněné zpracování ............................................................... 288 11.5 Generování výstupu .................................................................. 291 11.5.1 11.5.2 11.5.3 11.5.4
12.2 Webové služby à la REST ........................................................... 335 12.3 AJAX ..................................................................................... 337
13. Zápis XML ........................................................................ 345 13.1 Ruční generování XML ............................................................... 345 13.2 Generování pomocí DOM ............................................................ 348 13.3 Využití třídy XMLWriter ............................................................. 350
Závěr ..................................................................................... 353 Použitá literatura .................................................................... 355 Rejstřík .................................................................................. 357
Obsah
Předmluva
Předmluva Když jsem před více než deseti lety psal předmluvu k dnes již legendární knize PHP – tvorba interaktivních internetových aplikací [11], slíbil jsem v ní, že na doprovodný web knihy umístím informace o práci s formátem XML, které se do knihy již nevešly. Z časových důvodů k naplnění tohoto slibu nikdy nedošlo. Jako satisfakci proto přijměte tuto knihu. Její náplní je pouze XML a jeho použití v PHP. Deset let je dlouhá doba, ale myslím, že čekání se vyplatilo. Podpora XML byla v PHP až do jeho verze 5.0 poměrně partyzánská. A teprve od verze 5.2 lze PHP považovat za jazyk, ve kterém se dá s XML rozumně pracovat. O významu XML dnes již není potřeba nikoho přesvědčovat. Ať se nám to líbí nebo ne, XML je zkrátka všude a ve webových aplikacích je potřeba s tímto formátem pracovat. Ať už se jedná o importy a exporty dat, transformaci dat pro prezentační vrstvu nebo backend pro AJAXovou aplikaci. Cílem této knihy je naučit vás používat všechna existující rozhraní pro práci s technologiemi XML, která PHP nabízí. Popsán a vysvětlen je však i samotný jazyk XML, jeho syntaxe a navazující technologie jako XML schémata, dotazovací jazyk XPath a transformační jazyk XSLT. Pro pochopení výkladu tak není nutná žádná velká předchozí zkušenost s XML – vše potřebné je průběžně vysvětleno. V dnešní době začínají být papír a knihy považovány za příliš konzervativní médium. Asi jsem staromilec a mám tištěné knihy rád. Nicméně na adrese http://www.kosek.cz/knihy/ phpxml/ najdete další informace související s knihou – příklady ke stažení, opravy chyb apod. Máte-li ke knize nějaké připomínky, uvítám je na mojí emailové adrese <[email protected]>. Na vzniku knihy má zásluhu mnoho lidí. Nevyčerpatelnou trpělivost prokázali šéfredaktoři počítačové redakce Miroslav Lochman a Daniel Vrba, kteří vydrželi pět let čekat na dokončení knihy. Pokud v knize nebude příliš chyb, je to zásluha korektorky Zuzany Vrbové a redaktorky Evy Steinbachové. Největší dík však patří mé ženě Lence a dětem, kteří trpělivě snášely mé útěky k počítači při psaní knihy. Přeji vám příjemné čtení knihy. Jirka Kosek Liberec, 10. dubna 2009
Předmluva
Vývoj moderních webových aplikací klade na vývojáře vysoké nároky, je potřeba znát široké spektrum technologií. Počínaje jazyky HTML a CSS pro definici samotné stránky a jejího vzhledu, přes Javascript pro vytváření vysoce interaktivních aplikací, po nějaký serverový jazyk jako je PHP. Každá větší aplikace navíc potřebuje někam ukládat data, typicky do databáze. K tomu je potřeba znát principy protokolu HTTP a vědět, jak obcházet jeho limity. A aby toho nebylo málo, na mnoha místech se vývojář webové aplikace setká i s formátem XML. Když XML v polovině 90. let minulého století vznikalo, původní smělé plány byly, že zcela nahradí jazyk HTML při doručování obsahu do prohlížeče. Tato myšlenka se však ukázala jako příliš revoluční a navíc problémy spojené s jazykem XHTML a jeho podporou v prohlížečích v očích mnoha webových vývojářů nevrhly na XML příliš růžové světlo. Nicméně technologie XML jsou dnes pevnou součástí mnoha webových technologií, formátů a protokolů, takže je potřeba práci s tímto formátem ovládat. Kde se na webu s XML může vývojář setkat? Syntaxi XML využívají mnohé prezentační formáty – počínaje jazykem XHTML, přes stále populárnější vektorový grafický formát SVG, až po jazyky pro definici uživatelského rozhraní v moderních RIA (Rich Internet Application) prostředích, jako je XAML v Silverlightu a MXML ve Flashi. Pokud tedy vaše skripty v PHP v minulosti generovaly převážně HTML kód, časem budou přicházet požadavky na dynamickou tvorbu modernějších, na XML založených, formátů. XML dnes zcela dominuje na poli publikování metainformací. Jedná se například o formáty pro publikování přehledů nových článků, jako jsou RSS či Atom. Protože začleňování sémantiky ve strojově čitelné podobě přímo do webových stránek je stále v plenkách, mnoho vyhledávačů nabízí vlastní formáty, ve kterých jim můžete předávat informace vylepšující vyhledávání – například Google Sitemap nebo Google Base. Další využití XML je pro komunikaci a předávání dat. XML se využívá jednak pro výměnu dat mezi backendy jednotlivých aplikací a dále pak v AJAXových aplikacích pro zasílání aktualizací dat do prohlížeče. „Enterprise“ aplikace pak pro samotnou komunikaci nevyužívají prosté XML, ale komplexnější mechanismus webových služeb. Diverzita koncových zařízení používaných pro přístup k webu se také stále zvyšuje. Webové stránky se už neprohlížejí jen z klasického počítače, ale i z chytrých telefonů nebo různých PDA. Mnohé aplikace potřebují uspokojivě řešit možnost kvalitního tisku. Už nestačí vytvořit aplikaci, která výstupy generuje jen jako HTML. Pro jednotlivá koncová zařízení je potřeba generovat odlišné formáty výstupu a webovou aplikaci je potřeba obohatit o flexibilní prezentační vrstvu. Pro vytvoření takové vrstvy lze využít i jazyk XML a stylové technologie jako XSL.
Úvod
Úvod
Úvod
16
PHP a XML
Výše uvedený výčet toho, kde se na webu můžeme setkat s XML, jistě není úplný. Pouze potvrzuje to, že webový vývojář se dnes neobejde bez znalosti tohoto formátu a práce s ním. Tato kniha vás naučí vše potřebné o formátu XML a navazujících technologiích a o tom, jak lze s tímto formátem pracovat v PHP. První kapitola je určená zejména pro čtenáře, kteří ještě neznají formát XML. Seznámí se zde se syntaxí jazyka a naučí se ji kontrolovat. Druhá kapitola pak stručně shrnuje a ukazuje, jaké možnosti pro práci s XML nabízí PHP. Je to ideální místo pro porovnání jednotlivých přístupů pro práci s XML. Nemusíte tak číst celou knihu, ale v této kapitole zjistíte, jaký způsob práce s XML je pro vás nejvhodnější a ten si dále podrobněji nastudujete v odpovídající samostatné kapitole. Třetí kapitola přímo nesouvisí s XML, ale ukazuje, jak lze v PHP částečně obejít chybějící podporu znakové sady Unicode, kterou využívá i jazyk XML. Následují čtyři kapitoly, které podrobně popisují jednotlivá rozhraní pro práci s XML – SimpleXML, SAX, DOM a XMLReader. Výběr vhodného rozhraní záleží na povaze dat, která čtete, a na tom, jak je potřebujete zpracovat. Osmá kapitola seznamuje s dotazovacím jazykem XPath, který nabízí jednoduchou a zároveň mocnou metodu pro vyhledávání a výběr dat v dokumentech XML. Kromě samotného dotazovacího jazyka je zde samozřejmě popsáno, jak jej používat v kombinaci s rozhraními DOM a SimpleXML. Další dvě kapitoly se věnují kontrole (validaci) dokumentů XML. Zvláště v otevřeném prostředí internetu je potřeba počítat s nejhorším a všechny vstupy do aplikace pečlivě kontrolovat. Pro dokumenty XML takovou kontrolu nabízejí schémata, která dokáží popsat povolenou strukturu a datové typy dokument XML. Devátá kapitola tak popisuje nejpoužívanější schémové jazyky a v desáté kapitole je pak ukázáno, jak pomocí nich prakticky v PHP kontrolovat data uložená v XML. Jedenáctá kapitola vysvětluje základy jazyka XSLT a jeho použití v PHP. XSLT je nejvhodnější prostředek pro transformace dokumentů XML do dalších formátů, včetně formátu HTML. Nalezne tak uplatnění například v prezentační vrstvě webové aplikace. Následující dvanáctá kapitola se pak věnuje komunikaci mezi aplikacemi s využitím XML. Popsány jsou jak klasické webové služby, tak jednodušší mechanismy jako REST a AJAX. Poslední třináctá kapitola pak ukazuje možnosti pro generování dokumentů XML na výstupu skriptu. Kniha je zaměřena zejména na vysvětlení principů a na ukázky využití jednotlivých technologií a knihoven PHP. Ve většině případů jsou popsány všechny možnosti jednotlivých knihoven PHP. Nicméně kniha primárně neslouží jako referenční příručka, pro tyto účely je vhodné nahlížet i do dokumentace PHP na adrese http://docs.php.net/manual/en/. Všechny příklady byly testovány s PHP ve verzi 5.2 a v současné době nic nenasvědčuje tomu, že by se v blízké době mělo v jazyce PHP měnit něco, co by způsobilo nefunkčnost skriptů. Většina použitých knihoven je standardní součástí PHP. Chcete-li používat XSLT, je potřeba do PHP přidat modul php_xsl, pro webové služby je zapotřebí modul php_soap a kapitola o Unicode využívá některé funkce z modulu php_mbstring. Dlouhé řádky ve výpisech, které musely být rozděleny, jsou označeny pomocí znaku ‚►‘.
Úvod
Syntaxe XML Chceme-li pracovat s dokumenty XML, musíme samozřejmě vědět, jak tyto dokumenty vypadají. V této kapitole se proto seznámíme se syntaxí jazyka XML a dalšími jeho rysy, které bychom měli znát. Znáte-li již XML dobře, a zajímá vás jen, jak se s ním pracuje v PHP, můžete tuto kapitolu směle přeskočit.
1.1 Elementy a struktura dokumentu Každý XML dokument se skládá z elementů. Elementy se v textu vyznačují pomocí tzv. tagů. Většině elementů odpovídají dva tagy – počáteční a koncový. <para>Toto je obsah elementu para. Ukázka obsahuje jeden element para. Jeho obsah je vyznačen pomocí tagů <para> (počáteční tag) a (ukončovací tag). Jen na okraj poznamenejme, že výše uvedená ukázka je asi nejjednodušším dokumentem XML, který vůbec můžeme vytvořit. Názvy tagů se zapisují mezi znaky ‚<‘ a ‚>‘. Ukončovací tag má před svým názvem ještě znak ‚/‘, aby se odlišil od počátečního.
1.1 Elementy a struktura dokumentu
1. Syntaxe XML
1.
18
PHP a XML *
GHNODUDFH ;0/
LQVWUXNFHSUR]SUDFRYiQt
DWULEXW HOHPHQW RGEHUDWHO
NRPHQWiĜ
REVDK HOHPHQWX SROR]ND
SRþiWHþQtWDJ
NRQFRYêWDJ
REVDK HOHPHQWX FHQD Qi]HYDWULEXWX
KRGQRWD DWULEXWX
Obrázek 1.1: Základní části dokumentu XML Některé elementy nemusejí mít žádný obsah. Můžeme je samozřejmě zapisovat tak, že za počátečním tagem uvedeme hned ten koncový. <para>Toto je obsah elementu para. A tohle také. Není to však příliš pohodlné, a proto můžeme v XML použít ještě jednu variantu tagu, která říká, že element nemá žádný obsah. Počáteční tag ukončíme dvojicí znaků ‚/>‘ místo pouhého ‚>‘ a koncový tag vynecháme. <para>Toto je obsah elementu para. A tohle také. Každý dokument XML musí obsahovat pro všechny počáteční tagy odpovídající koncový tag, nebo musí být počáteční tag zapsán jako element s prázdným obsahem. Následující ukázky jsou ukázkami špatných dokumentů, které nevyhovují specifikaci XML. <para>Toto je obsah elementu para. A tohle také. Ukázka je chybná, neboť tag není ukončen. <para>Toto je obsah elementu para. A tohle také.
1. Syntaxe XML
PHP a XML
19
Počáteční tag <para> není ukončen a k ukončovacímu tagu v dokumentu neexistuje odpovídající počáteční tag. Chybou rovněž je, když se elementy v dokumentu kříží. Ukázka překřížení elementů Každý dokument XML musí být celý obsažen v jednom elementu. Následující ukázka tedy nepředstavuje správný dokument XML. Pokusný První Druhý Třetí
nadpis odstavec odstavec odstavec
Stačí však přidat jeden element, který vše „obalí“, a vše je v pořádku.
nadpis odstavec odstavec odstavec
1. Syntaxe XML
<článek> Pokusný První Druhý Třetí článek>
1.2 Datový model dokumentu Viděli jsme, že elementy můžeme do sebe zanořovat, takže element může obsahovat další elementy nebo text. Elementy tak vytvářejí hierarchickou stromovou strukturu. Každý dokument XML si proto můžeme představit jako strom, jehož jednotlivé uzly odpovídají jednotlivým elementům (šedé uzly v obrázku), případně textu uvnitř elementů (bílé uzly v obrázku).
osoba <jméno>Jan
Novák
42
jméno
příjmení
věk
Jan
Novák
42
Uzly odpovídající textovému obsahu elementů jsou ve stromu vždy na nejnižší úrovni listů a už na ně nemohou být navěšeny žádné další uzly. V případě, že má element tzv. smíšený obsah, jsou jeho dětmi ve stromové reprezentaci jak textové uzly, tak uzly odpovídající elementům.
1.2 Datový model dokumentu
20
PHP a XML
p
Kouření škodí zdraví.
Kouření
b
zdraví.
škodí Prázdné elementy se ve stromu dokumentu objeví jako uzly, které už také nemají žádné děti.
p
První řádka Druhá řádka
První řádka
br
Druhá řádka
Výše popsanému stromovému modelu dokumentu XML se říká infoset [7]. Abstraktní datový model infosetu stručně řečeno říká, že dokument XML je stromová struktura složená z jednotlivých uzlů. Uzlů je přitom několik typů (elementy, textové uzly, atributy, komentáře, instrukce pro zpracování, jmenné prostory a další). U každého uzlu pak infoset definuje několik jeho vlastností jako jméno, rodiče, seznam dětí apod. Na infosetu je tak založena většina jazyků a rozhraní, které jsou vystaveny nad XML. Je to pochopitelné, protože při práci s dokumentem XML nás většinou zajímá jeho struktura a obsah zachycený v elementech, a infoset nabízí právě tento pohled na dokument XML. Většinou nás totiž nezajímá pohled na dokument XML jako na textový soubor, ve kterém se vyskytují speciální značky oddělené pomocí znaků ‚<‘ a ‚>‘ od ostatního textu, protože bychom se museli sami starat o syntaktickou analýzu takového zdrojového textu.
1.3 Atributy Elementy jsou základním prostředkem pro členění informací uvnitř dokumentu XML. Kromě elementů lze pro zachycení informací využít atributy. Atributy se vždy zapisují k počátečnímu tagu elementu. Nějaká tajná informace. V naší ukázce jsme atributu zabezpečení přiřadili hodnotu důvěrné. Hodnotu atributu musíme vždy uzavřít do uvozovek nebo do apostrofů. U jednoho tagu lze použít více atributů najednou, stačí je oddělit mezerou. Nějaká tajná informace.► odstavec>
1. Syntaxe XML
PHP a XML
21
U jednoho elementu se přitom nemohou použít dva atributy se shodným názvem. V mnoha případech je jedno, zda nějakou informaci uložíme jako element, nebo atribut. Srovnejte například následující dva fragmenty dokumentu XML: <jméno>Pepa <jméno>Pepa 42
1.4 Zápis vyhrazených znaků Vzhledem k tomu, že se znak ‚<‘ používá pro oddělení tagů od okolního textu, není možné jej zapsat do dokumentu jen tak. Musíme ho opsat jako znakovou entitu <. Vyřešte nerovnost 3x < 5 Vidíme, že odkaz na znakovou entitu začíná znakem ‚&‘, proto i tento znak musíme do dokumentu vkládat opisem &. Křupavé rohlíčky vám dodá pekařství Žemlička & syn Pokud potřebujeme uvnitř hodnoty atributu použít zároveň uvozovky i apostrofy, s výhodou využijeme odpovídající opisy " a '. XML definuje ještě pátou znakovou entitu >, která zastupuje znak ‚>‘. Tento znak však ve většině případů můžeme zapisovat přímo bez nutnosti opisu.1 Tabulka 1.1: Předdefinované znakové entity XML Entita Znak <
Opis je nutný pouze v případě, že by se v dokumentu vyskytovala sekvence znaků ‚]]>‘. Ta se musí přepsat jako ]]>.
1.4 Zápis vyhrazených znaků
1. Syntaxe XML
Nicméně z praxe postupně vyplynulo několik pravidel, která vám pomohou vybrat si, zda je v dané situaci lepší použít element nebo atribut. S pravidly se seznámíme v části 9.6.1.
22
PHP a XML
1.5 Názvy elementů a atributů XML je (na rozdíl například od HTML) citlivé na velikost písmen. Počáteční a koncový tag se proto musí shodovat i ve velikosti písmen. Následující element je chybný, protože si neodpovídá počáteční a koncový tag: Tento element je zapsán špatně. Samotná jména elementů a atributů mohou přitom být vytvářena poměrně volně. První znak jména musí být písmeno nebo podtržítko, další znaky mohou navíc obsahovat i čísla, tečku a pomlčku. Písmena přitom mohou být i z jiné než anglické abecedy. Jména elementů a atributů tak můžeme psát klidně česky, nebo třeba rusky v azbuce: <имя>Булгаковимя>
1.6 Deklarace XML Každý dokument XML by měl začínat deklarací XML, ve které určíme, jakou verzi XML používáme a v jakém kódování je soubor uložen. <jméno>Jan
Novák
42 Každá aplikace, která podporuje XML, musí umět zpracovat soubor uložený v kódování UTF-8 nebo UTF-16. Proto bychom měli dokumenty XML přednostně ukládat a ostatním posílat v jednom z těchto kódování. V praxi se přitom častěji používá UTF-8 kvůli lepší kompatibilitě se staršími aplikacemi. Dokumenty je možné ukládat i v jiných kódováních, ale pak musíme toto kódování povinně určit v deklaraci XML a nemáme jistotu, že tento dokument zvládnou zpracovat všechny aplikace. V případě, že potřebujeme do dokumentu vložit nějaký znak, který buď není snadno dostupný na klávesnici nebo nejde reprezentovat v použitém kódování, můžeme do dokumentu vložit odkaz na číselný kód znaku v Unicode (podrobnější vysvětlení problematiky Unicode a kódování naleznete v kapitole 3). Předchozí dokument XML tak můžeme také zapsat jako: <jméno>Jan
Novák
42 Znak „á“ byl nahrazen svým číselným kódem (U+00E1) zapsaným v šestnáctkové soustavě. Kód je možné zapsat i v desítkové soustavě, v číselném odkazu na znak pak chybí ‚x‘.
1. Syntaxe XML
PHP a XML
23
<jméno>Jan
Novák
42
1.7 Komentáře Pokud potřebujeme v dokumentu něco vysvětlit nebo část textu dočasně skrýt, s výhodou k tomu použijeme komentář. Komentář je součástí dokumentu, ale parsery jej ignorují a není dále zpracováván. Komentář se zapisuje mezi znaky .
Komentář může obsahovat cokoliv, kromě posloupnosti znaků --. Z toho vyplývá, že komentáře do sebe bohužel nemůžeme zanořovat. V komentáři dokonce můžeme používat tagy atd. Jsou však zcela ignorovány. To se hodí pro dočasné vyřazení části dokumentu ze zpracování. <para>První odstavec. <para>Třetí odstavec.
1.8 Sekce CDATA Pokud potřebujeme do dokumentu vložit větší kus textu, kde se hojně používají znaky se speciálním významem jako ‚<‘, ‚>‘ a ‚&‘, je nepohodlné je zapisovat pomocí znakových entit. V takových případech se používá tzv. sekce CDATA. Oceníme ji zejména v případech, kdy je součástí XML dokumentu kód nějakého programu nebo HTML či XML kód. Použití sekcí CDATA si ukážeme na následujícím dokumentu. <script type="text/javascript">Ahoj"); }]]> Bez použití sekce CDATA by byl zápis přece jen poněkud krkolomný. <script type="text/javascript"> for (i=0; i < 10; i++) { document.writeln("
Ahoj
"); }
1.7 Komentáře
1. Syntaxe XML
24
PHP a XML
Obecně se tedy sekce CDATA zapisují jako . Text přitom může obsahovat cokoliv, kromě sekvence znaků ]]>. Konstrukce CDATA existuje v XML pouze pro větší pohodlí autorů, kteří zapisují XML kód ručně. Nepřidává do XML žádnou novou sémantiku, jde jen o alternativní syntaxi.
1.9 Instrukce pro zpracování XML dokumenty mohou být zpracovávány různými programy. Někdy může být užitečné do dokumentu uložit řídící informace, které jsou určeny pouze pro některý program. Můžeme tak do dokumentu zařadit odkaz na styl definující zobrazení v prohlížeči, formátovacímu programu můžeme naznačit, kde má zalomit stránku. Moderní skriptové jazyky pro generování dynamických webových stránek se také zapisují přímo do těla dokumentů. Pro všechny tyto účely má XML k dispozici standardní způsob pro zařazení nestandardních informací. Na libovolné místo dokumentu (kromě značkovaní – podobně jako u komentářů) můžeme vložit instrukce pro zpracování (processing instructions). Tyto instrukce XML parser ignoruje, předá je nadřazené aplikaci – záleží na ní, zda je nějak využije. Syntaxe instrukcí je velice jednoduchá. «identifikátor» «data»?> Pomocí «identifikátoru» můžeme rozlišovat jednotlivé druhy instrukcí – do jednoho dokumentu můžeme umístit instrukce pro několik různých programů. Samotná «data» instrukce mohou mít libovolný tvar, ale nesmějí obsahovat sekvenci znaků ?>. Pomocí instrukcí pro zpracování lze do dokumentů zařadit například příkazy skriptovacího jazyka PHP. <dokument> Dnešní datum je <para>Nějaké důležité informace. Pomocí instrukcí pro zpracování se k XML dokumentu připojují i styly definující zobrazení v prohlížeči. Téma týdneXML a stylové jazykyJiří Kosek ...
1. Syntaxe XML
PHP a XML
25
1.10 Automatická kontrola syntaxe Splňuje-li dokument všechna výše uvedená pravidla, je syntakticky v pořádku a říkáme o něm, že je správně strukturovaný (well-formed).
<jméno>Jan
Novák
42 Prohlížeče se většinou zastaví na první chybě, kterou naleznou (viz obrázek 1.2). Mnoho parserů je dostupných i v podobě jednoduchého programu, který můžeme spouštět z příkazové řádky. Velice rychlý a na funkce bohatý je parser xmllint2, který pro práci s XML používá stejnou knihovnu libxml2 jako PHP. Následující ukázka zobrazuje výstup programu xmllint při zpracování dokumentu z předešlého příkladu. $ xmllint --noout dokument-s-chybou.xml dokument-s-chybou.xml:3: parser error : Opening and ending tag mismatch: jméno ► line 3 and jémno <jméno>Jan ^ dokument-s-chybou.xml:6: parser error : Opening and ending tag mismatch: věk ► line 5 and osoba ^ dokument-s-chybou.xml:6: parser error : Premature end of data in tag osoba line 2 ^
1.11 Jmenné prostory Jedním ze základních cílů jazyka XML je poskytnout aplikacím formát, ve kterém půjde vyměnovat informace po celém světě. Pro dosažení tohoto úkolu je však potřeba zajistit, aby byly elementy používané v dokumentech jednoznačně identifikované a navzájem rozlišitelné. Jinak nebudeme například schopni rozlišit, zda element název popisuje název knihy v katalogu knihkupectví, nebo obchodní název firmy ve výpisu z obchodního rejstříku, nebo ještě něco úplně jiného. Nutnost jednoznačného rozlišení elementů je důležitá v těch případech, kdy přesně nevíme, jaké informace zpracovávaný dokument obsahuje, nebo zpracováváme komponovaný dokument, který obsahuje elementy z několika různých oblastí. 2
http://xmlsoft.org/
1.10 Automatická kontrola syntaxe
1. Syntaxe XML
Správnou syntaxi si můžeme nechat zkontrolovat pomocí tzv. parseru. Jednoduchý parser XML je dnes obsažen v každém webovém prohlížeči, stačí v něm otevřít dokument XML a v případě chyby dostaneme chybové hlášení. Můžeme si to vyzkoušet na následujícím dokumentu, který obsahuje dvě chyby – překlep v názvu koncového tagu jméno a neukončený element věk.
26
PHP a XML
Obrázek 1.2: Zobrazení chyby v dokumentu XML Problém jednoznačné identifikace elementů v dokumentech XML řeší jmenné prostory. Používáme-li v dokumentu XML jmenné prostory, není už element jednoznačně identifikován jen svým jménem, ale kombinací jména a jmenného prostoru. Jmenný prostor má přitom podobu adresy URI, která zajišťuje možnost celosvětově vytvářet nová URI a přitom zachovat jejich unikátnost. Důležité je uvědomit si, že URI adresa v tomto případě slouží jen jako identifikátor, aplikace pracující s XML se nikdy nesnaží z této adresy získat nějaký dokument. Pro rozlišení dvou dříve zmíněných významů elementu název tak můžeme použít dva různé jmenné prostory. V následující fiktivní syntaxi doplníme před názvy elementů URI jmenného prostoru. <{http://knihkupectvi.cz/katalog}název>Čuk a Gek{http://knihkupectvi.cz/► katalog}název> <{http://justice.cz/ns/or}název>Grada{http://justice.cz/ns/or}název> Vidíme, že kombinace URI jmenného prostoru a název elementu je teď už jednoznačná a je možné odlišit, kdy se o jaký název jedná. Zároveň však vidíme, že výše uvedený zápis by byl velmi nepohodlný a zdlouhavý. Ve skutečnosti se ani nejedná o syntaxi, která by fungovala, šlo mi jen o naznačení principu. V dokumentech se pro usnadnění zápisu příslušnosti elementu do nějakého jmenného prostoru používá jedna z následujících dvou syntaxí. První možností je použití implicitního (výchozího) jmenného prostoru. Chceme-li, aby nějaký element a všichni jeho potomci (tj. elementy v něm obsažené) patřily do nějakého jmenného prostoru, stačí když u tohoto elementu nadeklarujeme požadovaný jmenný prostor jako implicitní pomocí atributu xmlns. Následující ukázka je dokument v jazyce
1. Syntaxe XML
PHP a XML
27
XHTML, kde všechny elementy patří do jmenného prostoru http://www.w3.org/1999/ xhtml.
Druhá varianta spočívá v deklaraci prefixu, který zastupuje zvolený jmenný prostor. Tento prefix se pak zapisuje před jména všech elementů patřících do jmenného prostoru. Deklarace prefixu jmenného prostoru se provádí pomocí atributu ve speciálním tvaru: xmlns:«prefix»="«URI»" Náš ukázkový XHTML dokument proto můžeme zapsat také následujícím způsobem: Ukázka XHTML stránkyUkázka XHTML stránkyVšechny elementy v tomto dokumentu patří do jmenného prostoru XHTML. Zápisu jména elementu ve tvaru html:title se říká kvalifikované jméno elementu (QName). To se skládá z prefixu (html) a z lokálního jména (title). Kombinace lokálního jména a jmenného prostoru, pro který je prefix deklarován, společně jednoznačně identifikuje element. Prefix ovšem může být libovolný, slouží jen jako pomůcka pro zkrácení zápisu. Vždy je však důležité, jaký jmenný prostor zastupuje. Jmenné prostory nacházejí uplatnění zejména v dokumentech, které se skládají z různých sad značek. V praxi je takových případů hodně. Můžeme mít například XHTML dokument, který obsahuje vložené obrázky ve formátu SVG a matematické vzorce v MathML. Elementy těchto tří značkovacích jazyků jsou přitom ve zvláštních jmenných prostorech, abychom je dokázali rozlišit. Styly zapsané v jazyce XSLT zase používají jmenné prostory k odlišení výkonných instrukcí XSLT od elementů, které se jen kopírují na výstup transformace. Následující ukázka zachycuje dokument v XHTML, který obsahuje vložený fragment kódu s obrázkem v SVG.
1.11 Jmenné prostory
1. Syntaxe XML
Ukázka XHTML stránky
Ukázka XHTML stránky
Všechny elementy v tomto dokumentu patří do jmenného prostoru XHTML.
28
PHP a XML
Ukázka XHTML stránky se SVG obrázkem
Ukázka XHTML stránky se SVG obrázkem
<svg:svg width="4in" height="3in" viewBox="0 0 400 400"> <svg:title>Žlutý kruh s červeným nápisem <svg:g> <svg:circle style="fill: yellow; stroke: blue" cx="200" cy="200" r="150"/> <svg:text x="80" y="200" style="font-size: 36px; font-family: Verdana; color: red; fill: red">Dobrou chuť Deklarace prefixu jmenného prostoru nebo implicitního jmenného prostoru platí pro element, ve kterém je uvedena, a pro všechny jeho podelementy. Pokud však na některém z podelementů prefix nebo implicitní jmenný prostor předefinujeme, platí nová definice. Předchozí dokument tak můžeme zapsat i následujícím způsobem: Ukázka XHTML stránky se SVG obrázkem
Ukázka XHTML stránky se SVG obrázkem
<svg xmlns="http://www.w3.org/2000/svg" width="4in" height="3in" viewBox="0 0 400 400"> Žlutý kruh s červeným nápisemDobrou chuť V takto složených dokumentech přitom jmenné prostory neslouží jen k odlišení jednotlivých elementů, ale především k jejich jednoznačné identifikaci. Ví-li prohlížeč, že nějaké elementy patří do jmenného prostoru XHTML, a jiné zase do jmenného prostoru SVG, může je podle toho interpretovat. Ukazuje to obrázek 1.3, kde je přímo ve stránce vložený SVG obrázek vykreslen.
1. Syntaxe XML
29
1. Syntaxe XML
PHP a XML
Obrázek 1.3: Zobrazení XHTML stránky s vloženým obrázkem SVG Obrázek 1.4 zachycuje případ, kdy jsme elementy SVG neumístili do jmenného prostoru, prohlížeč je tedy nerozpoznal a nezpracoval jako SVG obrázek.
Obrázek 1.4: Zobrazení XHTML stránky se špatně vloženým SVG Atributy se v běžných případech do jmenného prostoru neumisťují, a proto se na ně ani nevztahuje implicitní jmenný prostor. Chápe se to tak, že atribut patří vždy k elementu, u kterého je uveden, a tento element už do nějakého jmenného prostoru patří. Nicméně i atributy patřící do nějakého jmenného prostoru mají své uplatnění. Říká se jim globální atributy a jedná se o obecné atributy, které lze použít u jakýchkoliv elementů a přidávají jim speciální sémantiku. Např. v jmenném prostoru http://www.w3.org/2001/ XMLSchema-instance jsou k dispozici atributy určující umístění schématu nebo neurčenou hodnotu.
1.11 Jmenné prostory
30
PHP a XML
<jméno>Jenda Atributy noNamespaceSchemaLocation a nil jde použít u libovolného elementu a právě proto, aby nemohlo dojít ke kolizi s jinými atributy, jsou umístěny ve speciálním jmenném prostoru. Podobně fungují atributy standardu XLink, které umožňují z libovolného elementu udělat odkaz. Například: <para xmlns:xlink="http://www.w3.org/1999/xlink">Pro zjištění délky řetězce můžeme použít funkci strlen.
1.12 Práce s bílými znaky V dokumentu XML se obvykle vyskytuje velké množství bílých znaků (mezer, konců řádek, tabulátorů), které slouží pro zpřehlednění zápisu – obvykle se každý element uvádí na novém řádku a velikost jeho odsazení odpovídá hloubce jeho zanoření do ostatních elementů. Tyto bílé znaky však obecně nejde pokládat za zbytečné, a proto jsou vždy parserem předány aplikaci k dalšímu zpracování. Bílé znaky použité pro zpřehlednění zápisu se v datovém modelu dokumentu XML většinou projeví jako textové uzly, které se skládají pouze z těchto bílých znaků. Popsané chování demonstruje i následující ukázka. Strom dokumentu XML obsahuje mezi všemi elementy textové uzly s bílými znaky. Je důležité si uvědomit, že tyto uzly jsou součástí dokumentu a při zpracování s nimi počítat. <jméno>Jan
Novák
42
osoba
↲␣␣
jméno
Jan
1. Syntaxe XML
↲␣␣
příjmení
Novák
↲␣␣
věk
42
↲
PHP a XML
31
V našem příkladě je evidentní, že textové uzly s bílými znaky jsou skutečně pro obsah dokumentu zcela zbytečné a bylo by možné je ignorovat. Knihovna libxml2 dokonce umožňuje tyto uzly automaticky odstranit. Jak by dokument dopadl po odstranění uzlů s bílými znaky, můžeme zkontrolovat i pomocí řádkové utility xmllint. $ xmllint --noblanks osoba.xml <jméno>Jan
Novák
42
<dokument>
První odstavec obsahuje na první pohled smíšený obsah. Tady se nám <em>Jan Novák neslije dohromady.
<em>Jan Novák
Kdybychom teď nechali parser odstraňovat uzly s bílými znaky, chybně v druhém odstavci odstraní mezeru mezi slovy Jan a Novák, a dostaneme tak nesmyslný text. $ xmllint --noblanks odstavec.xml <dokument>
První odstavec obsahuje na první pohled smíšený obsah. Tady se nám <em>Jan Novák neslije dohromady.
<em>Jan► em>Novák
Proč se tak stalo? Důvod je jednoduchý, parser se bez nějakých přídavných znalostí nemůže správně rozhodnout, kdy se jedná o smíšený obsah a kdy ne. U prvního odstavce šlo jednoznačně o smíšený obsah, protože se na stejné úrovni vyskytoval text i elementy. V druhém odstavci se však uvnitř elementu p objevily jen bílé znaky a další elementy. Nešlo jej tedy rozlišit od situace, kdy jsou uzly s bílými znaky v dokumentu čistě pro okrasu. Vidíme tedy, že říkat parseru o odstranění bílých znaků je bezpečné pouze v případě, kdy jsme si jistí, že v dokumentu nepoužíváme smíšený obsah. Pokud to neuděláme, hrozí, že při zpracování dokumentu přijdeme o některé podstatné informace. Aby mohl parser správně poznat, které elementy obsahují smíšený obsah a které ne, muselo by k němu být připojené schéma dokumentu. Z něj lze poznat, které elementy obsahují jen další podelementy a které mají smíšený obsah. Kdybychom k dokumentu doplnili například DTD (více si o nich povíme v části 9.2), odstranění bílých znaků proběhne bezpečně.
1.12 Práce s bílými znaky
1. Syntaxe XML
V praxi se však setkáme i s případy, kdy je odstranění textových uzlů s bílými znaky nežádoucí. Jedná se o tzv. smíšený obsah. Elementy se smíšeným obsahem jsou takové elementy, které mohou obsahovat jak přímo text, tak další podelementy. Typickým příkladem smíšeného obsahu jsou odstavce – ty obsahují text, ale v něm se mohou vyskytovat další elementy například pro zvýraznění textu nebo pro vytváření odkazů. Textové uzly s bílými znaky ve smíšeném obsahu často nesou důležitou informaci, jako mezery mezi slovy. Vše ukazuje následující dokument.
32
PHP a XML
]> <dokument>
První odstavec obsahuje na první pohled smíšený obsah. Tady se nám <em>Jan Novák neslije dohromady.
První odstavec obsahuje na první pohled smíšený obsah. Tady se nám <em>Jan Novák neslije dohromady.
<em>Jan► em> Novák
Vidíme, že uzly s bílými znaky se odstranily jen mezi elementy dokument a p tak, jak to určuje DTD. Element p je definován jako smíšený obsah, a proto se v něm bílé znaky neodstraňují. Bílým znakům jsme se věnovali poněkud více, protože je to téma, ve kterém má mnoho lidí poměrně dost nejasností. Je to umocněno i tím, že výchozí konfigurace parseru MSXML od Microsoftu se chová nestandardně a všechny uzly s bílými znaky vypouští. Každý parser navíc provádí určité úpravy bílých znaků, aby usnadnil práci aplikaci, která dokumenty XML zpracovává (v našem případě tedy PHP skriptu). První úprava spočívá v normalizaci znaků pro konec řádku. Různé počítačové platformy používají různé znaky pro konec řádku – ve Windows je to sekvence znaků CR LF, na unixech znak LF a na Macovi CR. Parser XML proto všechny tyto kombinace vždy převede na znak LF (kód tohoto znaku je 10). O něco komplexnější je normalizace hodnot atributů. Nejprve jsou v hodnotě atributu znormalizovány znaky konce řádku na LF a poté se všechny bílé znaky (tedy LF, mezery a tabulátory) převedou na mezery.3
1.13 Skládání dokumentů Jsou situace, kdy se kousek textu nebo značkování v dokumentu opakuje na několika místech a my ho nechceme opisovat pořád dokola. Jindy zase chceme určitou část kódu XML používat ve více různých dokumentech XML najednou. Pro oba tyto úkoly nabízí XML nástroje – jednak starší založené na entitách a novější založené na standardu XInclude.
3
Je-li navíc atribut deklarován v DTD a má jiný typ než CDATA, je normalizace ještě složitější. V případě potřeby se na její popis můžete podívat do specifikace XML http://www.w3.org/TR/REC-xml/#AVNormalize.
1. Syntaxe XML
PHP a XML
33
1.13.1 Entity Každý dokument XML se může skládat z několika entit. Všechny dokumenty, které jsme zatím viděli, se skládaly pouze z jedné entity, která tvořila celý dokument. Na začátku dokumentu v deklaraci typu dokumentu můžeme definovat jednu nebo více entit, na které se pak můžeme dále v těle dokumentu odkazovat. Každý odkaz na entitu se nahradí fragmentem kódu XML, který zastupuje. Deklarace entit se nejčastěji uvádějí v tzv. interní podmnožině:
Entity je možné definovat i v externí podmnožině, která obvykle obsahuje schéma dokumentu v podobě DTD a používá se ve více dokumentech. Podrobnější výklad tohoto způsobu najdete v 9.2. Deklarace entity má přitom tvar: Takto deklarovanou entitu pak můžeme použít v dokumentu pomocí odkazu na entitu, který má tvar: &«název entity»;
1.13.1.1 Interní textové entity Interní textové entity umožňují deklarovat entitu, která zastupuje často používaný text, kus XML kódu nebo třeba jen znak těžko dostupný na klávesnici. Odkaz na tuto entitu pak můžeme podle libosti používat dále v dokumentu v obsahu elementů nebo atributů. Příklad 1.1: Ukázka interních textových entit – syntaxe/interni-entity.xml ]> <manuál> &program; - Uživatelská příručkaProgram &program; můžete spustit výběrem příkazu v menu.► odstavec> Odinstalování aplikace &program; není možné. Při zpracování dokumentu se odkazy na entity automaticky nahradí textem, který entita zastupuje. Ověřit si to můžeme zase pomocí programu xmllint a jeho volby --noent. $ xmllint --noent interni-entity.xml
1.13.1 Entity
1. Syntaxe XML
…
34
PHP a XML
]> <manuál> Headache 2.7 - Uživatelská příručkaProgram Headache 2.7 můžete spustit výběrem příkazu v menu.► odstavec> Odinstalování aplikace Headache 2.7 není možné.
1.13.1.2 Externí textové entity Externí textové entity umožňují dokument složit dohromady z několika samostatných souborů. Je to užitečné zejména tehdy, kdy potřebujeme ručně zpracovávat dlouhý dokument. Rozdělením dokumentu do několika souborů získáme kratší dokumenty, které se snadněji editují. Problém externích entit je v tom, že samostatné entity nemohou obsahovat vlastní deklaraci typu dokumentu, a tudíž mohou používat pouze entity definované v „hlavním“ dokumentu a nelze je zpracovat samostatně (používají-li entity). Příklad 1.2: Externí entita – syntaxe/kapitola.xml ÚvodTak tohle je text samostatné kapitoly.Trošku ten text ještě prodloužíme. Příklad 1.3: Soubor načítající externí entitu – syntaxe/externi-entity.xml ]> <manuál> Uživatelská příručka &uvod; V místě odkazu na entitu &uvod; se do dokumentu vloží celý obsah externího souboru kapitola.xml. Můžeme se o tom přesvědčit. $ xmllint --noent externi-entity.xml ]> <manuál> Uživatelská příručkaÚvodTak tohle je text samostatné kapitoly.
1. Syntaxe XML
PHP a XML
35
Trošku ten text ještě prodloužíme. V deklaraci entity za klíčovým slovem SYSTEM můžeme použít jakoukoliv adresu URI, nejčastěji se proto používá adresa URL, ať už relativní nebo absolutní.
1.13.2 XInclude
Všechna tato omezení řeší standard XInclude. Ten definuje element include ve jmenném prostoru http://www.w3.org/2001/XInclude. Sémantika tohoto elementu je taková, že element se nahradí souborem, na který ukazuje jeho atribut href. Příklad 1.4: Složení dokumentu pomocí XInclude – syntaxe/xinclude.xml <manuál> Uživatelská příručka <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="kapitola.xml"/> Pro otestování skládání entit můžeme opět využít xmllint, tentokrát mu však pomocí parametru --xinclude řekneme, aby vyhodnotil elementy XInclude, na které v dokumentu narazí. $ xmllint --xinclude xinclude.xml <manuál> Uživatelská příručkaÚvodTak tohle je text samostatné kapitoly.Trošku ten text ještě prodloužíme. XInclude umožňuje nahradit i interní entity, byť už ne tolik elegantním způsobem. Definice všech textů, které se mají opakovaně používat, můžeme umístit do samostatného souboru a každému elementu přiřadit unikátní identifikátor, jak ukazuje následující příklad. Příklad 1.5: Definice sdílených elementů – syntaxe/definice.xml <definice> Headache 2.7
1.13.2 XInclude
1. Syntaxe XML
Skládání dokumentů pomocí externích entit má některé nevýhody – externí entity nemohou mít vlastní deklaraci typu dokumentu a navíc je mechanismus entit nepřímý – nejdříve entitu deklarujeme a teprve poté ji používáme. Navíc syntaxe pro deklaraci entit poněkud vybočuje ze syntaxe pro zápis elementů a atributů, protože byla do XML převzata z jeho historického předchůdce jazyka SGML.
36
PHP a XML
V adrese elementu XInclude můžeme za znakem ‚#‘ uvést identifikátor elementu nebo i složitější XPointer výraz, které určí, jaká část z celého odkazovaného elementu se má do dokumentu vložit. Příklad 1.6: Nahrazení interních entit pomocí XInclude – syntaxe/xinclude-fragmenty. xml <dokument xmlns:xi="http://www.w3.org/2001/XInclude"> Uživatelská příručka <xi:include href="definice.xml#program"/> <xi:include href="definice.xml#xpointer(id('program')/text())"/> Po složení pak bude dokument vypadat následovně: $ xmllint --xinclude xinclude-fragmenty.xml <dokument xmlns:xi="http://www.w3.org/2001/XInclude"> Uživatelská příručkaHeadache 2.7 Headache 2.7 XInclude můžeme použít i pro vložení textového souboru, který se nemá chápat jako dokument XML obsahující značkování. Pomocí přídavných atributů určíme, že se jedná o textový soubor a v jakém je uložen kódování. Příklad 1.7: Vložení textového souboru pomocí XInclude <dokument xmlns:xi="http://www.w3.org/2001/XInclude"> Výpis PHP skriptu <xi:include href="demo.php" parse="text" encoding="iso-8859-2"/> $ xmllint --xinclude xinclude-text.xml <dokument xmlns:xi="http://www.w3.org/2001/XInclude"> Výpis PHP skriptu
1. Syntaxe XML
PHP a XML
37
1.14 Katalogové soubory Mnoho dokumentů XML nejde zpracovat samostatně, ale pro jejich korektní zpracování je potřeba načíst i další soubory s přídavnými informacemi. Vezměme si jako příklad stránku v jazyce XHTML. Začátek takové stránky vypadá následovně:
$ xmllint --noout --valid stranka.xhtml I s rychlým připojením k internetu bude validace trvat pár sekund, kdy čekáme, než se stáhne DTD. Co se stane, když nebudeme připojeni k síti? Parseru se nepodaří získat DTD a pokus o načtení dokumentu selže. $ xmllint --noout --valid stranka.xhtml stranka.xhtml:3: warning: failed to load external entity "http://www.w3.org/TR/► xhtml1/DTD/xhtml1-strict.dtd" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> ^ stranka.xhtml:4: validity error : Validation failed: no DTD found ! ^ stranka.xhtml:6: parser error : Entity 'ndash' not defined Slohová práce – Jan Novák ^ stranka.xhtml:12: parser error : Entity 'nbsp' not defined
Vidíme, že druhá řádka obsahuje deklaraci typu dokumentu, která ukazuje na DTD definující elementy a atributy přípustné uvnitř dokumentů XHTML. DTD je přitom umístěno na webovém serveru, takže například při pokusu o validaci dokumentu se musí celé DTD nejprve stáhnout a teprve poté se provede samotná validace.
38
PHP a XML
V našem případě tedy stačí stáhnout si DTD pro XHTML4 a uložit je do nějakého adresáře. V dalším textu budeme předpokládat, že jsme DTD uložili do adresáře c:\data\xhtml (resp. /data/xhtml na unixovém systému). V adresáři data si nyní vytvoříme katalogový soubor. Příklad 1.8: Ukázka katalogového souboru – data/catalog.xml Vidíme, že katalogový soubor mapuje veřejné identifikátory XHTML -//W3C//DTD XHTML 1.0 Transitional//EN a -//W3C//DTD XHTML 1.0 Strict//EN na lokální kopie DTD v podadresáři xhtml. Parseru nyní stačí říci, aby tento katalog používal. Program xmllint podporuje několik způsobů, jak určit umístění katalogového souboru. Prvním z nich je proměnná prostředí XML_CATALOG_FILES. Očekává se, že bude obsahovat absolutní URI ke katalogovému souboru. Tj. file:///c:/data/catalog.xml resp. file:///data/catalog.xml Způsob nastavení této proměnné záleží na použitém operačním systému. Můžeme využít například příkaz set. c:\data>set XML_CATALOG_FILES=file:///c:/data/catalog.xml c:\data>xmllint --noout --valid stranka.xhtml Vidíme, že validace teď funguje i bez připojení k síti a je velmi rychlá, protože se nečtou žádné další soubory ze sítě. Kromě využití proměnné XML_CATALOG_FILES můžeme na unixových systémech katalog uložit do souboru /etc/catalog, kde se standardně očekává. Neobsahuje-li deklarace typu dokumentu veřejný identifikátor, např.: Můžeme do katalogového souboru přidat i položky, které zajistí přesměrování pro systémové identifikátory: 4
http://www.w3.org/TR/xhtml1/xhtml1.zip
1. Syntaxe XML
PHP a XML
39
<system systemId="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" uri="xhtml/xhtml1-strict.dtd"/> <system systemId="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" uri="xhtml/xhtml1-transitional.dtd"/> Takto nastavené katalogové soubory umí využívat všechny XML funkce obsažené v PHP5. Používají se pro přesměrování načítání nejen pro DTD, ale i pro importované styly XSLT apod.
Jazyk XML a na něj navazující standardy definuje několik univerzálních atributů, které lze použít na libovolném elementu. Všechny tyto atributy jsou globální a patří do jmenného prostoru http://www.w3.org/XML/1998/namespace. Tento jmenný prostor je speciální a nemusí se pro něj deklarovat prefix. Existuje pro něj předdefinovaný prefix xml.
1.15.1 xml:lang Pomocí atributu xml:lang můžeme pro element určit jazyk, v jakém je zapsán jeho obsah. Tuto informaci pak mohou využívat různé aplikace, např. pro správné dělení slov nebo indexování textu. <para>Celá kapitola je česky. <para xml:lang="en">This is the only exception because it is in English. <para>Obsah předchozího elmentu byl anglicky. Jako hodnota atributu se uvádí kód jazyka podle BCP 475. Jazykový kód českého jazyka je cs, pro slovenštinu je sk a pro angličtinu en. Kompletní přehled kódů najdete například na adrese http://www.loc.gov/standards/iso639-2/langcodes.html. Lze používat i třípísmenné kódy, což je pro více exotické jazyky dokonce nutnost.
1.15.2 xml:space Jak jsme si již řekli, bílé znaky jsou v dokumentu XML důležité a parser (pokud ho k tomu nepřinutíme) je nijak automaticky neodstraňuje. Záleží pak na konkrétní aplikaci, jak se s bílými znaky dále vypořádá. Mnoho jazyků založených na XML – třeba XHTML nebo XSL-FO – vícenásobné výskyty bílých znaků nahrazují jednou mezerou. Jsou ale případy, kdy se nám to nehodí. XML proto nabízí možnost, jak aplikaci předat informaci o tom, že uvnitř elementu se nemají bílé znaky nijak upravovat. Informace se předává tím, že se k elementu přidá atribut xml:space a nastaví se na hodnotu preserve. Druhou možností je nastavit jej na hodnotu default, kdy se pak uplatní výchozí nastavení aplikace pro práci s bílými znaky.
5
http://www.ietf.org/rfc/bcp/bcp47.txt
1.15 Speciální atributy
1. Syntaxe XML
1.15 Speciální atributy
40
PHP a XML
Poznamenejme ještě jednou, že atribut xml:space slouží k ovlivnění toho, jak se k bílým znakům zachová aplikace, ne parser XML, který dokument načítá.
1.15.3 xml:id V mnoha případech se hodí, když můžeme element jednoznačně identifikovat. V XML k tomu slouží atribut xml:id, který elementu přiřadí jednoznačný identifikátor. V jednom dokumentu se přitom nemohou vyskytovat dva elementy se stejným identifikátorem. Atribut xml:id tak má podobnou úlohu jako primární klíč v relačních databázích. Hodnota atributu je přitom poměrně omezená. Identifikátor musí začínat písmenem nebo podtržítkem, za kterým následují další písmena, číslice, tečky, podtržítka nebo pomlčky. Následující fragment kódu ukazuje použití xml:id: Ze života hmyzuÚvod ... Na elementy s takto přiřazeným identifikátorem se pak můžeme snadno odvolávat v různých rozhraních a dotazovacích jazycích. Např. rozhraní DOM nabízí metodu getElementById(), která vrátí element s daným identifikátorem. Podobně se chová i funkce id() v jazyce XPath. Na elementy s identifikátorem se můžeme odvolávat i v XInclude.
1.15.4 xml:base Pomocí atributu xml:base jde změnit základní URL, s kterým se skládají relativní URL uvedená v dokumentu. Význam atributu a užití atributu si ukážeme na několika příkladech. Předpokládejme, že dokument uložený na adrese http://example.org/manualy/ instalace.xml má následující obsah. <manuál xmlns:xi="http://www.w3.org/2001/XInclude"> Instalační příručka <xi:include href="kapitoly/uvod.xml"/> <xi:include href="kapitoly/prvni_instalace.xml"/> <xi:include href="kapitoly/upgrade.xml"/> Elementy XInclude ukazují na jednotlivé kapitoly. Aby je mohl parser načíst, musí však pro jednotlivé kapitoly znát jejich absolutní URL adresu. Tu získá tak, že relativní adresy z atributu href složí se základní adresou dokumentu http://example.org/manualy/ instalace.xml. Po složení adres získáme následující absolutní adresy jednotlivých kapitol:
1. Syntaxe XML
PHP a XML
41
http://example.org/manualy/kapitoly/uvod.xml http://example.org/manualy/kapitoly/prvni_instalace.xml http://example.org/manualy/kapitoly/upgrade.xml V našem dokumentu XML jsme u každého relativního odkazu museli opakovat adresář kapitoly, ve kterém byly jednotlivé kapitoly umístěné. Obejít to jde právě pomocí atributu xml:base. Tento atribut umožňuje změnit základní URL pro odkazy uvedené v elementu s tímto atributem. Nové základní URL vznikne složením dosavadního základního URL s adresou uvedenou v xml:base. Následující příklad ukazuje, jak jsme si ušetřili zápis tím, že jsme základní URL změnili na http://example.org/manualy/kapitoly/. <manuál xmlns:xi="http://www.w3.org/2001/XInclude" xml:base="kapitoly/"> Instalační příručka
1. Syntaxe XML
<xi:include href="uvod.xml"/> <xi:include href="prvni_instalace.xml"/> <xi:include href="upgrade.xml"/> Atribut xml:base nalézá uplatnění nejen ve spojení s elementy XInclude, ale obecně s jakýmikoliv elementy, které vytvářejí nějaký druh odkazů – např. XLink, katalogové soubory apod.
1.15.4 xml:base
Přehled podpory XML v PHP5 V této kapitole si na několika jednoduchých příkladech ukážeme, jaké možnosti pro práci s XML nabízí PHP. Seznámíme se tak se základními přístupy k načítání a zpracování dokumentů XML, které jsou pak podrobněji rozepsány v následujících kapitolách. Podpora práce s XML v PHP prošla velmi bouřlivým vývojem. První zárodky knihoven pro práci s XML se objevily již ve verzi PHP3. Jednalo se však pouze o jednoduchý proudový parser XML (SAX) vystavený okolo knihovny expat. Práce s touto knihovnou nebyla nijak zvlášť pohodlná a hodila se opravdu jen pro načítání dokumentů s nepříliš složitou strukturou. Verze PHP4 se snažila podporu XML vylepšit, ale přiznejme si, že ne zrovna šťastným způsobem. Podpora XML byla velmi roztříštěná. Nově přibyla možnost načíst celý dokument do paměti jako DOM strom. Bohužel, rozhraní této knihovny se mezi jednotlivými verzemi PHP4.x měnilo a ani po změnách nebylo v souladu se standardem DOM rozhraní tak, jak ho definovalo konsorcium W3C. Knihovna pro práci s DOM byla vystavena nad knihovnou libxml2 a umožňovala i provádění dotazů v jazyce XPath.
2. Přehled podpory XML v PHP5
2. Přehled podpory XML v PHP5
2.
44
PHP a XML
Do PHP4 byla přidána i možnost provádění XSLT transformací. Nejprve byla přidána knihovna, která využívala český XSLT procesor Sablotron. Později byla přidána ještě druhá knihovna pro práci s XSLT založená na knihovně libxslt (ta je od stejného autora jako libxml2). Obě dvě knihovny mezi sebou byly samozřejmě nekompatibilní, navíc knihovna založená na Sablotronu neuměla spolupracovat s dokumenty reprezentovanými DOM stromem. Uvažovalo se tedy i o tom, že by se přidala ještě jedna implementace rozhraní DOM, kterou obsahoval Sablotron. Ve verzi PHP5 se proto vývojáři rozhodli tuto roztříštěnost sjednotit, což byl jistě správný krok. Jeho vedlejší důsledek je však ten, že rozhraní knihoven pro práci s XML nejsou mezi verzemi PHP4 a PHP5 stejná, takže téměř všechny skripty pracující s XML je potřeba při přechodu mezi těmito verzemi PHP přepsat. Podpora XML v PHP5 je vystavena okolo knihoven libxml2 a libxslt od Daniela Veillarda. Všechna rozhraní XML, která jsou nad nimi postavena, byla přepracována, aby se s nimi lépe pracovalo a aby odpovídala příslušným standardům (např. W3C DOM). Nicméně praxe ukázala, že v navržených rozhraních pro PHP5.0 nebyly obsaženy všechny důležité věci. Nedostatky byly odstraněny až ve verzi 5.1. Takže pokud máte možnost volby, doporučuji pro práci s XML používat verzi PHP5.1 nebo vyšší. Ve zbytku kapitoly si ukážeme, jak pomocí jednotlivých XML rozhraní můžeme převést jednoduchý dokument XML obsahující souhrn zpráv ve formátu RSS do podoby webové stránky. Na obrázku 2.1 se můžeme podívat na to, jak má vypadat výsledek skriptů zobrazený v prohlížeči. Příklad 2.1: Ukázkový dokument RSS – data/luparss.xml Lupa.cz http://www.lupa.cz/ <description>Server o českém Internetu csZazděný Telecom http://r.iinfo.cz/?f=rss&► amp;u=http%3A%2F%2Fwww.lupa.cz%2Fclanek.php3%3Fshow%3D3656 <description>Na Olšanské ulici v Praze, přímo před budovou Českého ► Telecomu, vyrostla včera dopoledne zeď. Postavila ji společnost Tele2 na oplátku ► za to, jak Český Telecom zazdívá její klienty. Chce tím upozornit na problém ► s pomalým přepojováním svých zákazníků na ústřednách Českého Telecomu, kvůli ► možnosti využívat pevnou volbu operátora. Dana Bérová: Byla to nabídka, která se neodmítá http://r.iinfo.cz/?f=rss&► amp;u=http%3A%2F%2Fwww.lupa.cz%2Fclanek.php3%3Fshow%3D3657 <description>Téměř před rokem byl u příležitosti prvního dne Invexu (6. ► října 2003) spuštěn testovací provoz Portálu veřejné správy. Hned druhý den ► byl portál vyhlášen Událostí roku české informatiky a telekomunikací 2003 a ►
2. Přehled podpory XML v PHP5
cenu šla přebírat šéfka úseku e-government ministerstva informatiky Dana ► Bérová. Rychlost je nanic, následuj instinkt http://r.iinfo.cz/?f=rss&► amp;u=http%3A%2F%2Fwww.lupa.cz%2Fclanek.php3%3Fshow%3D3658 <description>Už docela dlouho mám doma připojení k Internetu od UPC a ► pomalu to přestávám stíhat: z původních 128 kbit/s jsem se za pár let dostal ► až na dříve neuvěřitelných 1.536 kbit/s; tvrdí to alespoň mail, který jsem ► tento týden od UPC dostal. Platím pořád stejně, takže mohu být rád. No jo - ► ale co si mám vlastně s takovým pásmem počít? Za co dostali mobilní operátoři pokuty? http://r.iinfo.cz/?f=rss&► amp;u=http%3A%2F%2Fwww.lupa.cz%2Fclanek.php3%3Fshow%3D3655 <description>Propojovací dohody, které Český Mobil uzavřel s Eurotelem ► a s Radiomobilem, vyhodnotil Český telekomunikační úřad jako exkluzivní a ► operátorům přikázal jejich dodržování. ÚOHS je také vyhodnotil jako exkluzivní, ► ale kvalifikoval to jako porušení zákona o ochraně hospodářské soutěže, udělil ► za ně operátorům pokuty a zakázal jejich plnění. Překryvné sítě jako lék na neduhy Internetu http://r.iinfo.cz/?f=rss&► amp;u=http%3A%2F%2Fwww.lupa.cz%2Fclanek.php3%3Fshow%3D3654 <description>Jednoduchá architektura současného Internetu stále přináší ► řadu výhod zejména z hlediska jeho rozšiřování, znamená však ale i potíže se ► správou, bezpečností a celkovým zdravím a výkonem celosvětové sítě. Současný ► stav nedává mnoho šancí pro budoucí požadavky informační společnosti. Kudy ► tedy dál s Internetem?
2. Přehled podpory XML v PHP5
45
2. Přehled podpory XML v PHP5
PHP a XML
46
PHP a XML
Obrázek 2.1: Dokument RSS zobrazený jako stránka HTML
2.1 SimpleXML – jednoduše na věc Dokumenty XML mají hierarchickou strukturu tvořenou vnořením jednotlivých elementů. Hierarchické struktury lze v počítači reprezentovat mnoha způsoby, v poslední době je populární modelování pomocí objektů. Knihovna SimpleXML využívá právě tento způsob. Dokument XML načte celý do paměti do struktury objektů, jejichž jména odpovídají názvům elementů zpracovávaného dokumentu. Díky tomu se pak velmi jednoduše přistupuje k jednotlivým informacím. Pro vytvoření struktury objektů z dokument XML slouží funkce simplexml_load_file(), která jako parametr očekává jméno souboru ke zpracování. Výsledkem je objekt, který zastupuje celý dokument XML. $xml = simplexml_load_file("dokument.xml"); Podelementy jsou přitom dostupné jako členské proměnné. K elementu channel se tak dostaneme zápisem: $xml->channel
2. Přehled podpory XML v PHP5
PHP a XML
47
Tímto způsobem můžeme v úrovni XML přeskočit několik úrovní a podívat se třeba na název kanálu: $xml->channel->title Tento zápis nám již rovnou vrátí název kanálu v souboru RSS, protože element title už obsahuje jen text. V případě, že element obsahuje další podelementy, není mapován na řetězec, ale na pole objektů, které reprezentují jednotlivé podelementy. K druhé položce kanálu se proto dostaneme zápisem: $xml->channel->item[1] Všimněte si, že číslování začíná od nuly, jak je v PHP u polí obvyklé. Analogicky tak můžeme získat i název druhé položky v kanálu: $xml->channel->item[1]->title $xml:SimpleXMLElement +channel : SimpleXMLElement
1 1
+title : string = Lupa.cz +link : string = http://www.lupa.cz +description : string = Server o českém Internetu +language : string = cs +item[] : SimpleXMLElement
1 1
1
1
item[0]:SimpleXMLElement
item[1]:SimpleXMLElement
+title : string = Zazděný Telecom +link : string = http://r.iinfo.cz/?f=... +description : string = Na Olšanské ulici v Praze...
+title : string = Dana Bérová: Byla to nabídka... +link : string = http://r.iinfo.cz/?f=... +description : string = Téměř před rokem byl u...
*
…
Obrázek 2.2: V SimpleXML jsou jednotlivé části XML reprezentovány jako objekty a jejich členské proměnné SimpleXML zpřístupňuje nejen elementy a jejich obsah, ale i atributy. Atributy jsou reprezentovány asociativním polem, které je dostupné na objektu odpovídajícího elementu. Např. atribut version u elementu rss získáme pomocí zápisu: $xml['version'] Díky jednoduchému principu knihovny SimpleXML není problém načíst dokument RSS a vytvořit z něj webovou stránku, jak ukazuje příklad. Příklad 2.2: Čtení XML pomocí SimpleXML – prehled/simplexml.php
Příklad si zaslouží jistě pár komentářů. Můžeme si všimnout, že na veškeré vypisované hodnoty pocházející z dokumentu XML aplikujeme funkci htmlspecialchars(). Ta zajistí, že znaky, které mají v HTML speciální význam (např. ‚&‘ a ‚<‘), se do výstupu zapíší jako odpovídající entity & a <. Uvnitř hodnoty atributu se v HTML nesmí vyskytovat znak uvozovky, resp. apostrofu v závislosti na tom, jaký znak je použit pro uzavření hodnoty atributu. Při generování atributů proto funkci htmlspecialchars() předáváme jako druhý parametr konstantu ENT_QUOTES, která zajistí, že na entity se převedou i znaky uvozovek a apostrofů. Jednotlivé položky uvnitř kanálu se opakují, a proto je nejjednodušší je zpracovat pomocí cyklu: foreach($xml->channel->item as $zprava) Uvnitř cyklu pak bude proměnná $zprava postupně obsahovat objekty odpovídající jednotlivým elementům item. Knihovna SimpleXML je díky své jednoduchosti velmi oblíbená. Hodí se zejména pro zpracování menších dokumentů s jednoduchou a pravidelnou strukturou. Naopak práce s dokumenty, které používají jmenné prostory nebo smíšený obsah, už v SimpleXML tak bezproblémová není. Více se o této problematice můžete dozvědět v samostatné kapitole 4.
2. Přehled podpory XML v PHP5
PHP a XML
49
2.2 SAX – čteme pěkně popořádku Rozhraní SAX patří k jedněm z nejstarších rozhraní pro práci s XML. Původně bylo vyvinuto pro programovací jazyk Java, ale brzy bylo ve více či méně upravené podobě převzato i do dalších jazyků. Na rozdíl od rozhraní DOM a SimpleXML se SAX hodí pro čtení i hodně velkých dokumentů XML, protože se dokument nenačítá celý do paměti, ale čte se postupně sekvenčně. Během čtení dokumentu se aplikaci průběžně předávají informace o tom, co se v dokumentu nachází za informace. SAX parser pro každý důležitý prvek dokumentu, jako je počáteční a koncový tag, znaková data, komentář apod., vyvolá událost, kterou můžeme obsloužit. Jako parametry události se přitom předávají důležité informace, jako je například název elementu pro počáteční a koncový tag, text obsažený ve znakových datech apod.
startElement endElement Obrázek 2.3: SAX reprezentuje dokument XML jako proud událostí Práce s rozhraním SAX je poměrně komplikovaná, protože ke zpracování dokumentu XML dochází nepřímo v obsluze událostí. Ve skriptu proto musíme definovat funkce, které se postarají o obsluhu jednotlivých událostí. Tyto funkce je pak potřeba zaregistrovat v nově vytvořeném parseru a teprve na konec probíhá samotné čtení dokumentu XML a jeho předávání parseru ke zpracování. Příklad 2.3: Čtení XML pomocí rozhraní SAX – prehled/sax.php <meta http-equiv="content-type" content="text/html;charset=utf-8"> Přehled zpráv
2.2 SAX – čteme pěkně popořádku
xml_set_character_data_handler($parser, "characters"); // otevření XML dokumentu $fp = fopen("../data/luparss.xml", "r"); if (!$fp) die ("Nelze otevřít soubor."); // zpracování celého souboru while ($x = fread($fp, 4096)) { if (!xml_parse($parser, $x, feof($fp))) die (sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser))); } // uvolnění paměti alokované parserem xml_parser_free($parser); // pomocné proměnné pro uchovávání stavu čtení $inItem = false; $inLink = false; $inTitle = false; $inDescription = false; $title = ""; $link = ""; $description = ""; // obsluha začátku elementu function startElement($parser, $name, $attrs) { global $inItem, $inLink, $inTitle, $inDescription, $title, $link, $description; // zjistíme, zda jsme v další položce feedu if ($name == "item") { $inItem = true; return; } // zjistíme, zda jsme v nadpisu if ($name == "title") { $inTitle = true; $title = ""; return; } // zjistíme, zda jsme v adrese if ($name == "link") { $inLink = true; $link = "";
2. Přehled podpory XML v PHP5
PHP a XML
51
return; } // zjistíme, zda jsme v popisu if ($name == "description") { $inDescription = true; $description = ""; return; } }
// zjistíme, zda jsme na konci položky if ($name == "item" && $inItem) { $inItem = false; echo "
\n"; return; } // zjistíme, zda jsme na konci elementu a nejsme v položce // v tomto případě vypisujeme záhlaví if ($name=="link" && !$inItem) { $inLink = false; echo "
"; return; } // ukončení seznamu if ($name=="channel") { echo "
\n"; } // vypnutí příznaků na koncovém tagu if ($name=="item") $inItem = false; if ($name=="title") $inTitle = false;
2.2 SAX – čteme pěkně popořádku
2. Přehled podpory XML v PHP5
// zpracování konce elementu function endElement($parser, $name) { global $inItem, $inLink, $inTitle, $inDescription, $title, $link, $description;
52
PHP a XML
if ($name=="link") $inLink = false; if ($name=="desciption") $inDescription = false; } // obsluha znakových dat function characters($parser, $data) { global $inItem, $inLink, $inTitle, $inDescription, $title, $link, $description; // připojení právě přečteného textu do odpovídající pomocné proměnné // podle toho, v jakém jsme právě elementu if ($inLink) $link .= $data; if ($inTitle) $title .= $data; if ($inDescription) $description .= $data; } ?> SAX parser v PHP pochází ještě z dob PHP3, a proto nemá objektové rozhraní. Pracuje se s ním podobně jako se soubory nebo s připojením k databázi. Nově vytvořený parser dostane přiřazený svůj identifikátor: $parser = xml_parser_create("utf-8"); A tento identifikátor se používá v dalších funkcích pro určení parseru, na který se má funkce použít. Můžeme tak najednou pracovat s více dokumenty XML. Většinou si proto identifikátor parseru uložíme do nějaké proměnné, v našem příkladě se jedná o proměnnou $parser. Před dalším použitím parseru jej musíme nakonfigurovat. Při výchozím nastavení parser ignoruje velikost písmen, což je v rozporu se specifikací XML. Proto náš skript toto chování vypíná: xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); Dále parseru nastavíme, jakým funkcím má ke zpracování předávat události pro začátek a konec elementu a pro text uvnitř elementu. xml_set_element_handler($parser, "startElement", "endElement"); xml_set_character_data_handler($parser, "characters"); Všechny tři odpovídající funkce startElement(), endElement() a characters() jsou přitom definovány dále ve skriptu a k jejich vysvětlení se ještě vrátíme.
2. Přehled podpory XML v PHP5
PHP a XML
53
Nyní je již parser připraven na přijímání a zpracování dat. Můžeme proto otevřít soubor obsahující dokument XML: $fp = fopen("../data/luparss.xml", "r"); Nyní v cyklu budeme ze souboru číst bloky textu o velikosti 4 KiB: while ($x = fread($fp, 4096)) V proměnné $x tak budeme mít vždy kus vstupního dokumentu XML. Ten musíme předat parseru ke zpracování: xml_parse($parser, $x, feof($fp)) Funkce xml_parse() jako první parametr očekává identifikátor parseru, dále data ke zpracování a poslední parametr určuje, zda se jedná o poslední kus dat, který parseru předáváme. Parser budeme naposledy volat, až přečteme celý soubor a funkce feof() tedy bude vracet hodnotu true. V případě, že ve zpracovávané části dokumentu XML je nějaká syntaktická chyba, vrátí funkce xml_parse() hodnotu false, takže můžeme na chybu zareagovat a vypsat ji.
xml_parser_free($parser); Kód, který jsme si dosud ukázali, je vlastně stejný pro všechny aplikace, které používají rozhraní SAX. Odlišnosti jsou až v logice zpracování dat, která je zapsaná přímo do funkcí, které obsluhují jednotlivé události. Při bližším studiu zjistíme, že kód vytvářený pomocí rozhraní SAX není zrovna dvakrát přehledný. Je to způsobeno tím, že zpracování jedné informace je rozděleno na tři části. Dejme tomu, že chceme přečíst název položky v kanálu RSS: Rychlost je nanic, následuj instinkt Tento kousek kódu XML rozhraní SAX předá postupně jako tři události: 1. událost začátek elementu (startElement) – v ní bude předán název počátečního tagu title; 2. událost znaková data (characters) – v ní bude předán obsah elementu „Rychlost je nanic, následuj instinkt“; 3. událost konec elementu (endElement) – v ní bude předán název koncového tagu title. Obsluha události pro počáteční tag proto musí otestovat, zda se jedná o počáteční tag elementu title. Pokud ano, pak si musíme nastavit nějaký příznak, který bude indikovat, že jsme uvnitř elementu title. Tento příznak pak bude testovat obsluha události znakových dat, protože text nás v tomto případě zajímá pouze tehdy, pokud jsme uvnitř elementu title. A konečně obsluha koncového tagu detekuje, zda se jedná o koncový tag elementu title. Pokud ano, zpracuje data, která jsme si uložili během zpracování události pro znaková data, a vynuluje příznak přítomnosti uvnitř elementu title.
2.2 SAX – čteme pěkně popořádku
2. Přehled podpory XML v PHP5
Na závěr zpracování je slušné uvolnit paměť, kterou si parser alokoval pomocí:
54
PHP a XML
Protože většinou pracujeme s více elementy než s jedním, je výše popsaný kód uvnitř obsluhy každé události přítomen několikrát pro každý element, jehož obsah chceme nějakým speciálním způsobem zpracovat. I proto náš ukázkový skript používá několik globálních proměnných $inItem, $inLink, $inTitle a $inDescription. V nich se uchovává informace o tom, v jakém elementu se nacházíme. Funkce startElement() obsluhující počáteční tagy testuje vždy název elementu, který začíná, a podle toho nastaví odpovídající příznak a případně vynuluje proměnnou, která se používá pro uchovávání textového obsahu elementu. // zjistíme, zda jsme v nadpisu if ($name == "title") { $inTitle = true; $title = ""; return; } Funkce pro obsluhu události začátku elementu přitom musí vždy akceptovat tři parametry. Prvním je identifikátor parseru, druhým název elementu a konečně třetí parametr je pole obsahující hodnoty všech atributů uvedených u elementu. Funkce characters() obsluhující znaková data dostane jako parametry identifikátor parseru ($parser) a text ($data), který je uvnitř elementu. Uvnitř funkce se podle příznaku rozhodneme, do jakého elementu text patří a připojíme k pomocné proměnné. Např. o postupné zjištění obsahu elementu title se postará následující část funkce: if ($inTitle) $title .= $data; Poslední část logiky zpracování údajů je uložena ve funkci endElement(), která se stará o obsluhu události pro koncový tag. V parametrech dostane předán identifikátor parseru a název ukončovacího tagu. Podle toho, jaký element je ukončen, se vypíší odpovídající údaje nashromážděné v pomocných proměnných uvnitř události pro znaková data. Nakonec se ještě zruší příznak indikující, že jsme uvnitř nějakého elementu: if ($name=="title") $inTitle = false; Jak je vidět, je použití rozhraní SAX poměrně pracné, protože čtení dokumentu XML jako proudu událostí není vždy úplně přehledné. Tomuto modelu pro práci s XML se také někdy říká push model, protože parser do aplikace tlačí (angl. push) informace z dokumentu XML.
dokument XML
čtení a analýza dokumentu
SAX parser
proud události
Obrázek 2.4: Princip push modelu přístupu k dokumentu XML Podrobněji se s rozhraním SAX a jeho použitím seznámíme v kapitole 5.
2. Přehled podpory XML v PHP5
PHP skript
PHP a XML
55
2.3 DOM – načteme to do paměti Rozhraní DOM (Document Object Model) je standardní rozhraní pro práci s dokumenty XML definované konsorciem W3C. Rozhraní definuje způsob, jakým se dokument XML mapuje na hierarchii objektů v paměti. Každé části dokumentu, jako je element, atribut, textový uzel a podobně, odpovídá v paměti jeden objekt. Model dokumentu vytvořený pomocí DOM je podobný stromu dokumentu složenému z jednotlivých uzlů, jak jsme si jej popsali v oddílu 1.2. Pomocí metod a vlastností dostupných na každém objektu můžeme zjišťovat druh uzlu, jaký ve stromu dokumentu XML zastupují, jejich název, obsah, seznam objektů reprezentujících dětské uzly, objekt zastupující rodiče uzlu atd. Podrobně si všechny dostupné metody popíšeme v samostatné kapitole 6. Dokument je v paměti reprezentován jako objekt, který je instancí třídy DOMDocument. Pomocí metody load() je možné vytvořit DOM reprezentaci dokumentu XML uloženého v souboru:
Metoda load() přečte celý dokument XML a v paměti z něj vytvoří stromovou reprezentaci. Jednotlivé uzly jsou reprezentovány objekty, jak ukazuje obrázek 2.5. Elementům odpovídají instance třídy DOMElement, atributům instance třídy DOMAttr a textovým uzlům pak instance třídy DOMText. U každého uzlu můžeme zjišťovat mnoho údajů, nejtypičtějšími je název uzlu a jeho hodnota. Tyto údaje jsou zachyceny i na obrázku – prostřední údaj každého uzlu je jméno uzlu a dolní údaj znázorňuje hodnotu uzlu. Protože všechny třídy použité při vytváření DOM stromu mají společného předka třídu DOMNode, mají mnoho společných metod a vlastností. Jednak se jedná o vlastnosti, které dovolují zjistit jméno uzlu (nodeName) či jeho hodnotu (nodeValue). Další vlastnosti umožňují pohyb po stromu dokumentu. Vlastnost childNodes vrací seznam dětských uzlů a naopak parentNode vrací rodiče. Používat tyto vlastnosti pro výběr určitých částí dokumentu je však většinou velmi pracné. Obvykle je mnohem pohodlnější pro výběr částí dokumentu využít dotazovací jazyk XPath, jak si ukážeme dále. Budeme-li však chtít zůstat u standardních nástrojů, které nabízí rozhraní DOM, může se nám hodit metoda getElementsByTagName(), která vrací seznam uzlů odpovídající elementům s daným názvem. Použití této metody ilustruje i následující příklad, který využívá rozhraní DOM pro zobrazení jednoduchého dokumentu RSS. Příklad 2.4: Čtení XML pomocí rozhraní DOM – prehled/dom.php <meta http-equiv="content-type" content="text/html;charset=utf-8"> Přehled zpráv
2.3 DOM – načteme to do paměti
2. Přehled podpory XML v PHP5
$doc = new DomDocument(); $doc->load("dokument.xml");
56
PHP a XML
DOMDocument #document
DOMElement
DOMAttr
rss
version 0.91
DOMElement channel
DOMElement
DOMElement
DOMElement
DOMElement
DOMElement
DOMElement
DOMElement
DOMElement
DOMElement
title
link
description
language
item
item
item
item
item
…
…
…
DOMText
DOMText
DOMText
DOMText
DOMElement
DOMElement
DOMElement
#text
#text
#text
#text
title
link
description
Lupa.cz
http://www.lupa.cz
Server o českém Internetu
cs
DOMText
DOMText
DOMText
#text
#text
#text
Rychlost je nanic, následuj instinkt
http://r.iinfo.cz/…
Už docela dlouho mám doma…
…
Obrázek 2.5: DOM strom pro ukázkový dokument // načtení dokumentu do paměti do DOM stromu $doc->load("../data/luparss.xml"); ?>
Celý skript pracuje na velmi jednoduchém principu. Nejprve do proměnné $doc načte DOM strom celého dokumentu. Následně je potřeba vypsat obsah elementů link a title. Nejjednodušší způsob, jak tyto elementy vybrat, je použít metodu getElementsByTagName(). Ta nám vrátí seznam elementů s daným jménem. Nás zajímá pouze první takový element, na který se můžeme odkázat pomocí zápisu:
Výraz přitom vrací objekt z DOM stromu, instanci třídy DOMElement. Můžeme na ní tedy volat libovolné metody nebo pracovat s libovolnými vlastnostmi, které tato třída nabízí. My potřebujeme znát textový obsah elementu. Ten jde získat tak, že projdeme všechny na něj navěšené textové uzly a spojíme je do jedné hodnoty. Tento postup by však byl velmi pracný a proto rozhraní DOM nabízí jednodušší postup. Na libovolném uzlu stromu můžeme číst vlastnost textContent, která vrací textovou hodnotu daného uzlu. Zápis $doc->getElementsByTagName("link")->item(0)->textContent tak vrací text, který je uzavřený v prvním elementu link vyskytujícím se v dokumentu. V další části skript vypisuje všechny položky. Pro výběr položek opět využijeme metodu getElementsByTagName() a její výsledek si uložíme do proměnné. $polozky = $doc->getElementsByTagName("item"); Výsledný seznam uzlů je instance třídy DOMNodeList. Tato třída definuje vlastnost length vracející počet uzlů v seznamu a metodu item(), která vrací uzel na dané pozici v seznamu. Tato vlastnost a metoda jsou dostačující pro napsání kódu, který projde v seznamu jeden uzel po druhém. V praxi se však využívá toho, že třída DOMNodeList implementuje rozhraní iterátoru a jde ji zpracovat klasickým příkazem foreach. foreach($polozky as $polozka) { … } Cyklus se provede pro každý element item, jemu odpovídající uzel DOM stromu bude uvnitř těla cyklu dostupný v proměnné $polozka. Toho využíváme i pro čtení podelementů položky, jako jsou link, title a description. Zavoláme-li totiž metodu getElementsByTagName() na jiném uzlu než kořenovém, prohledává jen potomky daného uzlu, a ne celý DOM strom. Využijeme ji proto pro pohodlné zjištění názvu a popisu položky a odkazu na zdroj, který popisuje. Podrobněji se s rozhraním DOM a jeho použitím seznámíme v kapitole 6.
2.3 DOM – načteme to do paměti
2. Přehled podpory XML v PHP5
$doc->getElementsByTagName("link")->item(0)
58
PHP a XML
2.4 XPath – rychle to najdeme Všechny předchozí příklady načítání dokumentu XML měly jednu věc společnou – v kódu bylo přesně vyjádřeno (byť různě úsporným způsobem), co přesně má program s dokumentem XML dělat, aby získal potřebné informace. Vývoj programování však spěje k tomu, aby se tento procedurální přístup používal co nejméně, a místo toho jsou stále oblíbenější deklarativní přístupy, kdy pouze specifikujeme, co chceme získat, a počítač se o to už nějak postará. Klasickým příkladem takového jazyka je dotazovací jazyk SQL, který umožňuje jednoduše a stručně formulovat požadavek na získání nějakých dat z relační databáze. Při použití SQL jsme přitom zcela odstíněni od toho, jak se k výsledku dotazu konkrétně dojde. Podobnou roli ve světě XML plní dotazovací jazyk XPath, ten umožňuje v jednoduché syntaxi zapsat dotaz, který vybere určité uzly nebo hodnotu z dokumentu XML. Jazyk XPath přitom jako mnoho jiných technologií operuje nad stromovou reprezentací dokumentu. Abychom mohli v PHP pokládat nad dokumentem XML dotazy, musíme jej nejprve načíst do paměti do klasického DOM stromu. $doc = new DomDocument(); $doc->load("dokument.xml"); Nad DOM stromem si pak můžeme vytvořit objekt, který dovoluje provádění dotazů v jazyce XPath: $xpath = new DOMXPath($doc); Ve své nejjednodušší podobě umožňuje XPath zapisovat dotazy, které jsou podobné cestě k nějakému souboru na disku. Nepohybujeme se však v adresářové struktuře, ale po stromové struktuře dokumentu XML. Například dotaz /rss/channel/title vrátí uzel odpovídající elementu title, který je uvnitř elementu channel, který je uvnitř elementu rss. Dotaz /rss/channel/item pak vybere elementy item, které jsou uvnitř elementu channel, který je uvnitř elementu rss. XPath dotaz můžeme vyhodnotit pomocí metody evaluate(): $vysledek = $xpath->evaluate("/rss/channel/title"); Je-li výsledkem dotazu seznam uzlů, je tento seznam vrácen jako instance třídy DOMNodeList. V případě, že nás zajímá jen textový obsah elementu, můžeme v XPathu použít funkci string() – například string(/rss/channel/link). Ve skriptu tak název kanálu můžeme velice jednoduše vypsat pomocí příkazu: echo $xpath->evaluate("string(/rss/channel/title)"); Použití XPathu pro vypsání informací z dokumentu RSS ukazuje následující příklad. Příklad 2.5: Dotazování pomocí XPathu – prehled/xpath.php <meta http-equiv="content-type" content="text/html;charset=utf-8"> Přehled zpráv
2. Přehled podpory XML v PHP5
PHP a XML
59
load("../data/luparss.xml"); // vytvoření objektu pro vyhodnocování dotazů XPath $xpath = new DOMXPath($doc); ?>
Ukázkový příklad je s využitím XPathu velmi jednoduchý. Využívá ještě jednu zajímavou možnost metody evaluate(). Zadáme-li jako druhý parametr nějaký uzel ve stromu dokumentu XML, vyhodnotí se dotaz relativně vzhledem k tomuto uzlu. Dotaz link proto vybere podelement link u aktuálně zpracovávané položky (element item). Zápisem string(link) získáme textový obsah elementu link. S dotazovacím jazykem XPath a možnostmi jeho použití v PHP se podrobně seznámíme v kapitole 8.
2.4 XPath – rychle to najdeme
2. Přehled podpory XML v PHP5
// výběr všech položek v kanálu $polozky = $xpath->evaluate("/rss/channel/item");
60
PHP a XML
2.5 XSLT – jazyk budoucnosti Protože je dnes stále více dat dostupných v XML, vznikla potřeba jazyka, který umožní jednoduše popsat způsob převodu mezi různými formáty založenými na XML. Právě XSLT je jazyk, který umí jednoduše popsat, jak se má dokument XML převést na dokument XML s jinou strukturou, případně do podoby stránky HTML nebo dokonce do čistého textu. XSLT lze použít i pro naší úlohu, protože potřebujeme převést RSS (což je jen specifický případ dokumentu XML) do formátu HTML. Podrobnější výklad základů jazyka XSLT naleznete v kapitole 11, nyní si jen na jednoduchém příkladě ukážeme, jak můžeme v praxi využít XSLT. Příklad 2.6: XSLT styl pro převod RSS do HTML – prehled/rss2html.xsl <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" encoding="utf-8" doctype-public="-//W3C//DTD HTML 4.01//EN"/> <xsl:template match="channel"> Přehled zpráv
XSLT pro svůj zápis používá syntaxi XML, proto musí začínat deklarací XML. Výkonné instrukce XSLT patří do jmenného prostoru http://www.w3.org/1999/XSL/Transform, pro který se obvykle používá prefix xsl. Celý styl se zapisuje dovnitř elementu xsl:stylesheet. Uvnitř stylu lze používat další instrukce. My například pomocí instrukce xsl:output nastavujeme parametry výstupu – v jakém bude kódování, jaký se použije formát (HTML/XML) a jak bude vypadat deklarace typu dokumentu: <xsl:output method="html" encoding="utf-8" doctype-public="-//W3C//DTD HTML 4.01//EN"/>
2. Přehled podpory XML v PHP5
PHP a XML
61
Nejdůležitější částí každého stylu jsou šablony (xsl:template). Každá šablona definuje, jak se bude zpracovávat určitá část vstupního dokumentu (nejčastěji nějaký konkrétní element). Náš styl obsahuje jen jednu šablonu, která definuje způsob zpracování elementu channel: <xsl:template match="channel"> … Šablona se pak chová tak, že v ní obsažené texty a elementy nepatřící do jmenného prostoru XSLT kopíruje na svůj výstup. Elementy patřící do jmenného prostoru XSLT jsou chápány jako instrukce, které XSLT procesor provádí. V naší šabloně jsou použity jen dvě instrukce xsl:value-of a xsl:for-each. Instrukce xsl:value-of slouží k vypsání výsledku XPath výrazu do výstupu. Takže například instrukce: <xsl:value-of select="title"/>
Instrukce xsl:for-each je naopak příkaz cyklu. Pro všechny uzly, které vybere XPath výraz uvedený v atributu select, se provede kód uvedený uvnitř této instrukce. Uvnitř cyklu se navíc aktuálním uzlem stává uzel, pro který se právě provádí tělo cyklu, takže XPath výraz title nyní vybírá název položky, ne kanálu v RSS dokumentu. Potřebujeme-li nějakou hodnotu vložit do atributu, nemůžeme použít instrukci xsl:value-of, protože syntaxe XML neumožňuje používat elementy uvnitř hodnot atributů a XSLT musí této syntaxi vyhovět. Uvnitř atributů proto můžeme výrazy jazyka XPath zapisovat do složených závorek ({…}). Aby bylo naše řešení kompletní, musíme samozřejmě umět na dokument XML aplikovat transformaci popsanou pomocí XSLT a její výsledek poslat do prohlížeče. V PHP je tento úkol velmi jednoduchý, protože obsahuje i knihovnu pro práci s XSLT. Stačí dokument i styl načíst jako DOM objekty do paměti, poté si vytvořit nový procesor XSLT (třída XSLTProcessor), načíst do něj styl z DOM stromu a spustit transformaci. Příklad 2.7: Transformace dokumentu XML – prehled/xslt.php load("../data/luparss.xml"); // načtení stylu XSLT $xsl = new DomDocument(); $xsl->load("rss2html.xsl"); // vytvoření procesoru XSLT $proc = new xsltprocessor(); $proc->importStylesheet($xsl);
2.5 XSLT – jazyk budoucnosti
2. Přehled podpory XML v PHP5
vypíše obsah elementu title, který je dítětem aktuálního uzlu. No a uvnitř šablony je aktuální ten uzel, který šablona právě obsluhuje, tedy element channel.
62
PHP a XML
// provedení transformace a vypsání výsledku echo $proc->transformToXML($xml); ?>
2.6 XMLReader – když se zamotáme do SAX Jak jsme viděli, rozhraní SAX je sice rychlé a nenáročné na zdroje, ale práce s ním není moc pohodlná. Je to způsobeno push modelem, který SAX používá (viz obrázek 2.4). Tuto nevýhodu SAXu překonávají novější rozhraní, která staví na tzv. pull modelu. Zatímco push parser (SAX) zahrnuje naši aplikaci proudem událostí, které zkrátka musíme obsloužit, pull parser aplikaci předá data, jen když si o to aplikace řekne. Data tedy čteme v tu chvíli, kdy je potřebujeme. Díky tomu může být kód aplikace mnohem přehlednější a přímočařejší.
dokument XML
čtení a analýza dokumentu
XMLReader
vyžádání další části dokumentu
PHP skript
Obrázek 2.6: Pull model čtení dat Pull parser je v PHP dostupný jako třída XMLReader. Díky této objektové obálce je práce s XMLReader velmi jednoduchá. Nejprve si musíme vždy vytvořit novou instanci parseru a pak říci, jaký dokument se bude načítat: $reader = new XMLReader(); $reader->open("dokument.xml"); V tomto okamžiku je dokument XML připraven ke čtení. V okamžiku, kdy náš skript bude chtít přečíst část dokumentu, stačí použít metodu read(), která přečte další část dokumentu XML (počáteční tag, obsah elementu apod.). V případě, že jsme na konci dokumentu, vrací metoda hodnotu false, v opačném případě true. Chceme-li tedy postupně přečíst celý dokument, stačí metodu volat v cyklu while: while ($reader->read()) { … } Na objektu XMLReader je dostupných několik vlastností, které umožňují zjišťovat informace o části dokumentu, na kterou jsme se právě přesunuli. Můžeme například zjišťovat druh uzlu (nodeType) a jeho název (name). Jsme-li nastaveni na elementu (tedy na jeho počátečním tagu), lze pomocí metody readString() přečíst celý textový obsah elementu. Použití těchto metod pro čtení dokumentu RSS demonstruje následující příklad. Příklad 2.8: Sekvenční čtení XML – prehled/xmlreader.php <meta http-equiv="content-type" content="text/html;charset=utf-8"> Přehled zpráv
2. Přehled podpory XML v PHP5
PHP a XML
63
open("../data/luparss.xml"); // dokud je co, čteme další část dokumentu XML while ($reader->read()) { // obsluha odkazu (element link) if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "link") echo "
// obsluha položky v kanálu if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "item") { echo "
"; // dokud je co, čteme další část dokumentu XML // předpokládáme přitom, že elementy jsou uvnitř elementu item while ($reader->read()) { if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "link") echo "
// obsluha názvu kanálu if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "title") $title = htmlspecialchars($reader->readString());
64
PHP a XML
V příkladě je použita konstanta XMLREADER_ELEMENT, která definuje kód uzlu, který odpovídá začátku elementu. Zároveň vidíme, že podle potřeby můžeme čtení z dokumentu XML zanořovat níže do struktury kódu – např. když chceme jinak zpracovat obsah elementu item. Práce s XMLReader je principem podobná jako sekvenční čtení souborů, a proto je mnoha programátorům velmi blízká. Navíc zachovává výhody rozhraní SAX, jako je rychlost a malá paměťová náročnost. Podrobněji se s XMLReaderem a jeho použitím seznámíme v kapitole 7.
2.7 Webové služby Webové služby je označení skupiny technologií, které umožňují jednoduchou komunikaci mezi aplikacemi. Komunikace je přitom obvykle zajišťována zasíláním zpráv pomocí protokolu HTTP. Na internetu je v současné době dostupných mnoho webových služeb, které zajišťují velmi různorodé služby – zjišťování předpovědi počasí, objednání letenky, prohledání katalogu internetového obchodu apod. Jakoukoliv z těchto služeb můžeme volat z naší aplikace. Stačí službě poslat zprávu ve formátu XML s odpovídajícími parametry a pak zpracovat odpověď, která bude opět zaslána jako XML. Protože by ruční generování a dekódování zasílaných zpráv bylo ve většině případů poměrně pracné, využívá se jiný přístup. Struktura zpráv a další potřebné údaje definující rozhraní webové služby jsou popsány ve speciálním souboru ve formátu WSDL. PHP umí tento popis rozhraní načíst a vytvořit objekt, jehož metody umožňují vyvolání webové služby. Tento objekt je pak rovnou naplněn daty vrácenými službou, abychom se nemuseli starat o jejich ruční extrakci z XML. Použití funkcí pro práci s webovými službami si ukážeme na jednoduchém příkladě. Služba popsaná pomocí dokumentu WSDL na adrese http://live.capescience.com/wsdl/ AirportWeather.wsdl umožňuje zjistit informace o aktuálním počasí na letišti určeném jeho kódem. My si napíšeme jednoduchou aplikaci, která po vybrání letiště odešle webové službě dotaz na počasí a takto získané informace o počasí pak vypíše. Příklad 2.9: Jednoduchý klient webové služby – prehled/pocasi.php <meta http-equiv="content-type" content="text/html;charset=utf-8"> Informace o počasí na vybraných letištích
Informace o počasí na vybraných letištích
2. Přehled podpory XML v PHP5
PHP a XML
65
// vyvolání webové služby $pocasi = $ws->getSummary($_GET["kod"]);
Kód letiště:
Umístění letiště:
location ?>
Teplota:
temp ?>
Vlhkost vzduchu:
humidity ?>
Vítr:
wind ?>
Obloha:
sky ?>
Při prvním voláním skriptu není předán formulářový parametr kod, a proto se zobrazí jednoduchý formulář pro výběr letiště. Data z tohoto formuláře jsou zpracovávána stejným skriptem. Je-li však již kód letiště zadán, vytvoří se klient pro přístup k webové službě: $ws = new SoapClient("http://live.capescience.com/wsdl/AirportWeather.wsdl");
2.7 Webové služby
2. Přehled podpory XML v PHP5
?>
66
PHP a XML
Třída SoapClient načte popis rozhraní WSDL ze zadané adresy a podle toho dynamicky vytvoří objekt, který bude mít metody odpovídající jednotlivým operacím podporovaným webovou službou. Skript pak volá metodu pro zjištění předpovědi počasí pro letiště určené svým kódem: $pocasi = $ws->getSummary($_GET["kod"]); Metoda automaticky obalí kód letiště potřebnými elementy XML, pošle jej přes HTTP webové službě a z dokumentu XML, který získá jako odpověď, automaticky vytvoří objekt $pocasi, jehož členské proměnné budou obsahovat získané hodnoty. Takže například aktuální teplotu na letišti získáme pomocí $pocasi->temp. Podrobněji se s webovýmu službami seznámíme v kapitole 12.
2.8 Závěr Viděli jsme, že PHP nabízí několik různých metod pro čtení dokumentů XML. Pro zpracování rozsáhlých dokumentů (větších než jednotky megabajtů) jsou vhodná jen rozhraní SAX a XMLReader. Tato rozhraní jsou navíc velmi rychlá. Můžeme-li si dovolit načíst celý dokument XML do paměti, máme na výběr mezi rozhraními SimpleXML a DOM. Pro dokumenty s jednoduchou strukturou je použití SimpleXML většinou jednodušší než použití DOMu. Nicméně DOM rozhraní narozdíl od SimpleXML umožňuje přístup ke všem informacím uloženým v dokumentu XML. Navíc rozhraní DOM umožňuje dokument XML v paměti i modifikovat. Máme-li již však dokument načtený celý do paměti pomocí DOM, je v mnoha případech vhodnější využít nějaký nástroj vyšší úrovně, jako je XPath nebo XSLT. Přístup k datům uloženým v XML a jejich zpracování je mnohem efektivnější než při použití nízkoúrovňových metod DOM.
2. Přehled podpory XML v PHP5
3. Jazyk XML jako znakovou sadu používá Unicode. Současná verze PHP však standard Unicode nepodporuje. V této kapitole se proto nejprve seznámíme s problematikou znakových sad a kódování a pak se podíváme na to, jak lze obejít chybějící podporu Unicode.
3.1 Znakové sady, kódování a Unicode Než se podíváme na možnosti PHP ohledně práce se znakovými sadami a kódováními, bude užitečné si vysvětlit pár základních pojmů.
3.1.1 Znaková sada Jestliže chceme, aby počítač uměl pracovat s textem, musíme nadefinovat, jaké znaky se v textu mohou vyskytovat. A protože počítače vnitřně reprezentují veškeré informace pomocí čísel, musíme těmto znakům přiřadit číselné kódy, které je budou zastupovat. Znaková sada je právě taková množina znaků, kde má každý znak přiřazený číselný kód. Znaková sada nám vymezuje repertoár znaků, které je možné v textu používat.
3.1 Znakové sady, kódování a Unicode
3. (Ne)podpora Unicode v PHP
(Ne)podpora Unicode v PHP
68
PHP a XML
Historický vývoj dal vzniknout desítkám a možná i stovkám různých znakových sad. Nejznámější z nich je asi znaková sada ASCII, která definovala 128 znaků, jež zahrnovaly písmena anglické abecedy, číslice, interpunkční znaménka a pár dalších speciálních znaků. Znaková sada ASCII tak byla vhodná pro zápis textů v angličtině, pro psaní kódů programů apod. Nešlo v ní však psát například české texty, protože chyběly znaky s diakritikou. Vzniklo tak mnoho znakových sad, které jsou rozšířením ASCII – definují celkem 256 znaků. Prvních 128 znaků je shodných s ASCII a druhých 128 může být použito právě pro národní znaky. Pro češtinu takových znakových sad existovalo několik, v dnešní době se používají zejména znakové sady ISO Latin 2 (ISO 8859-2) a Windows CP 1250. Tabulka 3.1 ukazuje, že v různých znakových sadách mohou mít znaky různé kódy nebo nemusí být vůbec definovány. Tabulka 3.1: Ukázka definice kódu znaku pro různé znakové sady Znak Kód v ASCII Kód v ISO Latin 2 Kód ve Windows CP 1250 A
65
65
65
á
–
225
225
Ž
–
174
142
3.1.2 Kódování Znaková sada definuje repertoár dostupných znaků a jejich číselných kódů. Abychom mohli text v nějaké znakové sadě uložit do souboru nebo přenést po síti, musíme jednotlivé kódy znaků převést na posloupnost bajtů, protože právě bajty jsou základní jednotkou, do které se ukládají informace v souborech nebo posílají po síti. Způsobu převodu číselného kódu znaku do posloupnosti bajtů se říká kódování. Protože historické znakové sady jako ASCII, ISO Latin 2 nebo Windows CP 1250 obsahovaly maximálně 256 znaků, šlo jako kódování použít obyčejnou identitu. Číselný kód znaku se rovnou zapsal jako jeden bajt, a bylo vystaráno.
3.1.3 Unicode Problém znakových sad jako ISO Latin X a CP 125X byl v tom, že byly navrženy pro ukládání textů v omezené množině jazyků. Kdybychom měli například text, který míchá třeba češtinu s ruštinou (která používá azbuku), neměli bychom k dispozici žádnou znakovou sadu, která by zahrnovala jak české znaky s diakritikou, tak i azbuku. S postupující globalizací a rozšiřováním počítačů vůbec začal být tento stav neudržitelný. Logickým východiskem proto bylo vytvoření univerzální znakové sady, která by zahrnovala znaky všech běžně používaných jazyků. Při jejím použití by pak bylo možné v textu používat libovolný v ní definovaný znak a bez problému tak míchat texty i v zcela odlišných jazycích. Tato univerzální znaková sada vznikla na začátku 90. let minulého století a jmenuje se Unicode. Je navržena tak, aby byla schopná reprezentovat více jak jeden milión znaků, i když v dnešní době je definováno „jen“ 96 382 znaků.1 Protože byl jazyk XML navrhován jako univerzální a musí být schopný reprezentovat informace v libovolném jazyce, používá se v něm pro zápis textu právě znaková sada Unicode. 1
Standard Unicode se neustále vyvíjí a jsou do něj podle potřeby přidávány nové znaky. Informace o počtu znaků se vztahuje k verzi Unicode 4.0.
3. (Ne)podpora Unicode v PHP
PHP a XML
69
Podobně je definován i jazyk HTML 4.0 – jako znakovou sadu používá Unicode, takže uvnitř dokumentu lze použít libovolný znak definovaný v rámci Unicode. Při studiu specifikací možná narazíte na to, že místo o Unicode hovoří o znakové sadě ISO 10646. Nenechte se tím zmást – jedná se o totožnou znakovou sadu, která definuje stejné znaky a přiřazuje jim stejné číselné kódy. Jediný rozdíl je v tom, že standardy Unicode a ISO 10646 jsou formálně vydávány různými organizacemi. Na obrázku 3.1 se můžeme podívat, jak vypadají definice znaků a jim odpovídajích číselných kódů ve standardu. Z tabulky je například patrné, že znak „ě“ má kód 11B v šestnáctkové soustavě (283 v desítkové). Pro označení znaku s určitým číselným kódem se často používá syntaxe U+«XXXX», kde «XXXX» je právě kód znaku vyjádřený v šestnáctkové soustavě. Písmeno „ě“ tak můžeme v této notaci zachytit jako U+011B.2 Jako u jakékoliv jiné znakové sady, i číselné kódy znaků Unicode musíme pro účely převodu do počítačové reprezentace převést na posloupnost bajtů. Vzhledem k velkému počtu znaků není už příliš vhodné používat kódování v podobě identity, kdy se kód znaku zapisuje přímo beze změn. Takové kódování existuje a jmenuje se UTF-32, ale prakticky se nepoužívá. Jeden znak zapíše do čtyř bajtů, ve kterých přímo reprezentuje číselný kód znaku. Jedná se tedy o velice neúspornou reprezentaci textu. Existují proto úspornější kódování UTF-8 a UTF-16. S nimi se v praxi setkáme často, například proto, že se jedná o preferovaná kódování pro dokumenty XML.
Podívejme se nejprve podrobněji na kódování UTF-8, které se dnes v prostředí internetu nejčastěji používá pro kódování textu v Unicode. Zvláštností UTF-8 je to, že jeden znak se může zakódovat do proměnlivého počtu bajtů (od jednoho do čtyř). Navíc je UTF-8 navrženo tak, aby bylo zpětně kompatibilní s ASCII. Máme-li tedy v Unicode text obsahující jen znaky z ASCII a zapíšeme jej v kódování UTF-8, vypadá výsledný soubor stejně, jako by byl rovnou vytvořen v ASCII. Je to možné díky tomu, že prvních 128 znaků Unicode bylo převzato z ASCII a UTF-8 je přímo kóduje jako odpovídající jednobajtovou hodnotu. Obsahuje-li text znaky Unicode s kódy většími než 128, jsou tyto znaky reprezentovány jako několik bajtů, mezi které se po jednotlivých bitech rozdělí hodnota číselného kódu znaku. Jak probíhá převod kódu znaku na sekvenci bajtů v UTF-8, zachycuje tabulka 3.2. Tabulka 3.2: Princip kódování v UTF-8 Rozsah kódů
Číselný kód Unicode
Výsledný počet bajtů
U+0000–U+007F
00000000 00000000 0xxxxxxx
1
0xxxxxxx
U+0080–U+07FF
00000000 00000yyy yyxxxxxx
2
110yyyyy 10xxxxxx
U+0800–U+FFFF
00000000 zzzzyyyy yyxxxxxx
3
1110zzzz 10yyyyyy 10xxxxxx
4
11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
U+10000–U+10FFFF 000uuuuu zzzzyyyy yyxxxxxx
Výsledné zakódování do UTF-8
Jak lze tuto tabulku využít pro kódování textu do UTF-8, si ukážeme na jednoduchém příkladě. Předpokládejme, že chceme do UTF-8 uložit text „á —“ (malé písmeno „á“, me2 Jazyk XML však používá vlastní notaci pro označení znaku s určitým číselným kódem. Písmeno „ě“ můžeme do dokumentu XML vložit mimo jiné pomocí zápisu ě nebo ě.
Reprodukce tabulky ze standardu Unicode byla do knihy zařazena s laskavým svolením Unicode, Inc. Obrázek 3.1: Ukázka definice znaků ze standardu Unicode zeru a pomlčku). Nejprve musíme zjistit, jaké jsou kódy těchto znaků v Unicode. Zjistíme, že se jedná o znaky s kódy U+00E1, U+0020 a U+2014. Už z toho je patrné, že první znak se zakóduje do dvou bajtů, druhý do jednoho a třetí dokonce do tří bajtů. Nyní si stačí kódy znaků převést do dvojkové soustavy a podle tabulky provést zakódování do UTF-8:
3. (Ne)podpora Unicode v PHP
PHP a XML
71
á (U+00E1) = 00000000 11100001 → 11000011 10100001 = C3 A1 (U+0020) = 00000000 00100000 → 00100000 = 20 — (U+2014) = 00100000 00010100 → 11100010 10000000 10010100 = E2 80 94 Vidíme, že text „á —“ se do UTF-8 zakóduje jako posloupnost bajtů C3 A1 20 E2 80 94. Dekódování probíhá přesně opačným způsobem. Uvidíme-li v nějaké aplikaci podivný text, kde se budou vyskytovat znaky jako „Ă“, „Ä“ a „Ĺ“ následované dalším nesmyslným znakem, znamená to obvykle, že si soubor uložený v kódování UTF-8 prohlížíme pomocí programu, který neumí provést správné dekódování sekvencí bajtů UTF-8 zpět na Unicode znaky a tyto znaky následně zobrazit.
Obrázek 3.2: Vzhled textu kódovaného v UTF-8 zobrazeného v režimu bez podpory UTF-8
Kódování UTF-16 je podstatně jednodušší než UTF-8. Je-li kód znaku menší než 65536, zapíše se v kódování UTF-16 jako jedno 16bitové slovo (2 bajty). Je-li kód znaku vyšší, zapíše se jako dvě 16bitová slova, do kterých se rozdělí bity z původní hodnoty podobně jako u UTF-8.
Rozsah kódů
U+0000–U+FFFF
Číselný kód Unicode
xxxxxxxx xxxxxxxx
U+10000–U+10FFFF 000uuuuu xxxxxxxx xxxxxxxx a
Počet bajtů
Tabulka 3.3: Princip kódování v UTF-16 Výsledné zakódování do UTF-16
2
xxxxxxxx xxxxxxxx
4
110110ww wwxxxxx 110111xx xxxxxxxxa
Platí přitom, že wwww = uuuuu – 1.
Zajímavé je, že Unicode nedefinuje žádné znaky s kódy mezi U+D800–U+DFFF, takže nedojde k nejednoznačnosti při kódování znaků do dvou 16bitových slov. Případům, kdy se znak kóduje do dvou 16bitových slov, se říká náhradní páry (surrogate pairs). Pojďme si nyní ukázat, jak by se do UTF-16 zakódoval text „á —“. Protože žádný z těchto tří znaků nemá kód vyšší než 65535, použije se vždy uložení do jednoho 16bitového slova.
3.1.3 Unicode
3. (Ne)podpora Unicode v PHP
3.1.3.2 UTF-16
72
PHP a XML
á (U+00E1) → 00 E1 (U+0020) → 00 20 — (U+2014) → 20 14 Text tedy bude uložen jako posloupnost bajtů 00 E1 00 20 20 14. Jenže ve skutečnosti to není tak jednoduché. Historicky existují dva způsoby ukládání 16bitových hodnot do paměti počítače. Jedné se říká velký endián (big endian) a v paměti jsou dva bajty 16bitové hodnoty uloženy v pořadí, které odpovídá jejich lidskému zápisu. Tento zápis je použit v příkladu. Druhá metoda označovaná jako malý endián (little endian) ukládá nejdříve méně významný bajt 16bitové hodnoty a pak ten více významný. V tomto případě by byl náš text zakódován jako E1 00 20 00 14 20. Aby aplikace, které čtou text v UTF-16, mohly mezi těmito dvěma variantami rozlišit, vkládá se na začátek souborů uložených v UTF-16 speciální znak U+FEFF, tzv. BOM (Byte Order Mark). Podle toho, zda soubor začíná sekvencí bajtů FE FF, nebo FF FE, můžeme snadno poznat, o jakou z variant UTF-16 se jedná. Není-li BOM v textu přítomen, musí být pořadí bajtů patrné z názvu kódování – hovoříme pak o kódování UTF-16BE, resp. UTF-16LE. Některé aplikace vkládají znak BOM i do textu uloženého v UTF-8, kde je to zcela zbytečné, protože UTF-8 dovoluje jen jedno pořadí bajtů. Vkládání tohoto znaku má však nepříjemné vedlejší účinky na interpret PHP (viz část 3.3.1). Kódování UTF-16 je sice jednodušší než UTF-8, nicméně pro přenos a ukládání dat se v internetu příliš nepoužívá, protože není zpětně kompatibilní s ASCII. Navíc pro texty v angličtině a v jazycích, které nepoužívají příliš často znaky nad rámec ASCII, je zápis v UTF-16 skoro dvakrát delší než v UTF-8. Nicméně UTF-16 je zase snazší na zpracování, a proto se v mnoha prostředích používá pro interní reprezentaci řetězců. UTF-16 používají vnitřně například Windows, Java nebo .NET.
3.1.3.3 Zapomeňte na staré znakové sady Jak jsme viděli, pro zápis textu v Unicode je nejlepší použít kódování UTF-8 nebo UTF-16. Existují však situace, kdy musíme používat nějaké starší softwarové nástroje, které tato kódování nepodporují. Ani pak však není nic ztraceno. To, čemu se v dobách před Unicode říkalo znaková sada – např. ISO Latin 2, Windows CP 1250 – lze dnes chápat jen jako speciální kódování znakové sady Unicode. Tato kódování se vyznačují tím, že jeden znak vždy ukládají do jednoho bajtu a jsou schopna reprezentovat jen vybraných 256 znaků z celého repertoáru Unicode. Převod kódu Unicode na hodnoty bajtů ve vybraném kódování je pak dán tabulkou, která jednotlivým znakům Unicode přiřazuje hodnoty.3 Například pro kódování Windows CP 1250 bychom zjistili, že znak „á“ U+00E1 je reprezentován jako bajt s hodnotu E1. Znak dlouhé pomlčky „—“ U+2014 je reprezentován jako bajt s hodnotou 97. Zapisujeme-li dokument XML v jiném kódování než UTF-16 nebo UTF-8, musíme použité kódování identifikovat v deklaraci XML pomocí jeho oficiálního názvu. Pro Windows CP 1250 má tento identifikátor tvar windows-1250, takže deklarace XML pak vypadá:
3 Překódovací tabulky pro všechna běžně používaná kódování lze získat na adrese ftp://ftp.unicode.org/Public/ MAPPINGS/.
šestnáctkové soustavě šestnáctkové soustavě v šestnáctkové soustavě desítkové soustavě
Protože je dnes většina datových formátů (XML a HTML nevyjímaje) postavena výhradně na znakové sadě Unicode, je pro zjednodušení terminologie nejjednodušší považovat původní znakové sady jako ISO-8859-2 nebo Windows-1250 jen za kódování schopná reprezentovat podmnožinu Unicode. Když už nyní víme, co je to znaková sada, kódování a Unicode, můžeme se konečně podívat na to, jak to vše souvisí s PHP. Zdála-li se vám úvodní exkurze do problematiky znakových sad a kódování příliš složitá, vězte, že i tak byla hodně zjednodušena pro snazší uchopení. [15]
Velký problém PHP5 je v tom, že i přes dávné sliby vývojářů4 vůbec neobsahuje podporu Unicode. Zatímco jiná prostředí pro tvorbu aplikací jako Java nebo .NET mají přímo datový typ znak Unicode a řetězec takových znaků, PHP nic takového nenabízí. PHP totiž ve skutečnosti nepracuje s textovými řetězci složenými ze znaků, ale s posloupnostmi binárních dat složených z bajtů. Jak se to projevuje prakticky? Představme si, že do proměnné pozdrav uložíme řetězec „čau“: $pozdrav = "čau"; Jeden by si mohl myslet, že proměnná $pozdrav obsahuje textový řetězec skládající se ze tří znaků „č“, „a“ a „u“. Ale ve skutečnosti se řetězec skládá z bajtů, které v souboru se skriptem tyto tři znaky reprezentovaly. Ty budou jiné, bude-li skript uložen v kódování windows-1250 nebo iso-8859-2. Jiné budou i v kódování UTF-8 a navíc to už nebudou bajty tři, ale dokonce čtyři, protože písmeno „č“ je v UTF-8 reprezentováno dvěma bajty. Na první pohled si tohoto drobného nedostatku nevšimneme, protože třeba při vypisování hodnot to nevadí. Použijeme-li příkaz: echo $pozdrav; tak se do výstupu zkrátka zapíší bajty odpovídající řetězci „čau“ v kódování skriptu, a pokud je kódování generované stránky stejné, prohlížeč zobrazí korektní text. 4