Copyright © 2001 Michal Kec
Obsah
Text, který právě začínáte číst je dokumentací k zápočtovému příkladu z předmětu IZI238: XML -- teorie a praxe značkovacích jazyků, vyučovaném od roku 2001 na Vysoké škole ekonomické v Praze.
Text byl napsán v textovém editoru programu FAR a v Notepadu. Celý dokument byl napsán a uložen ve formátu XML. S pomocí systému DocBook a stylů od Normana Walshe lze zdrojový XML dokument snadno převézt do různých formátu vhodných pro tisk, či publikaci na Internetu. Spolu se zdrojovým XML formátem je dokumentace k dispozici ve formátu HTML, RTF (ve verzi Word'95) a PDF.
Takto dokumentace, jakož i dokumentovaný zápočtový příklad je k dispozici na adrese http://sorry.vse.cz/~xkecm01/p_links.php#izi238
Jednoduše řečeno, jedná se o systém XML dokumentů a stylů umožňující vedení osobního, nebo firemního adresáře. Záznamem v adresáři je tedy jedna osoba a sledované údaje se dělí do několika skupin.
Každá skupina dále obsahuje elementy specifické pro danou kategorii. Každá osoba musí mít vždy právě jeden výskyt každé kategorie. Kategorie zaměstnání je nepovinná. Podrobnější informace o datové struktuře poskytne přiložený DTD.
<?xml encoding="windows-1250"?> <!ELEMENT adresar (osoba+)> <!ELEMENT osoba (osobni, bydliste, rodina, zamestnani?)> <!ATTLIST osoba id ID #REQUIRED jmeno CDATA #REQUIRED prijmeni CDATA #REQUIRED titul_pred CDATA #IMPLIED titul_za CDATA #IMPLIED> <!-- ========================================================= --> <!ELEMENT osobni (výška, váha, barva_očí?, barva_vlasů?, datum_narození, rodné_číslo, pohlaví, občanství?, národnost?)> <!ELEMENT výška (#PCDATA)> <!ATTLIST výška jednotka CDATA 'cm'> <!ELEMENT váha (#PCDATA)> <!ATTLIST váha jednotka CDATA 'kg'> <!ELEMENT barva_očí (#PCDATA)> <!ELEMENT barva_vlasů (#PCDATA)> <!ELEMENT datum_narození (#PCDATA)> <!ELEMENT rodné_číslo (#PCDATA)> <!ELEMENT pohlaví (#PCDATA)> <!ELEMENT občanství (#PCDATA)> <!ELEMENT národnost (#PCDATA)> <!-- ========================================================= --> <!ELEMENT bydliste (okres?, město, ulice?, psč, telefon*, fax*, e-mail*)> <!ELEMENT okres (#PCDATA)> <!ELEMENT město (#PCDATA)> <!ELEMENT ulice (#PCDATA)> <!ELEMENT psč (#PCDATA)> <!ELEMENT telefon (#PCDATA)> <!ATTLIST telefon uto CDATA '02'> <!ELEMENT fax (#PCDATA)> <!ATTLIST fax uto CDATA '02'> <!ELEMENT e-mail (#PCDATA)> <!-- ========================================================= --> <!ELEMENT rodina (manžel-ka?, dítě*)> <!ATTLIST rodina stav (svobodný|svobodná|ženatý|vdaná|vdovec|vdova|rozvedený|rozvedená) 'svobodný'> <!ELEMENT manžel-ka (#PCDATA)> <!ATTLIST manžel-ka id IDREF #IMPLIED jmeno CDATA #REQUIRED prijmeni CDATA #REQUIRED> <!ELEMENT dítě (#PCDATA)> <!ATTLIST dítě id ID #IMPLIED jmeno CDATA #REQUIRED prijmeni CDATA #REQUIRED> <!-- ========================================================= --> <!ELEMENT zamestnani (název, funkce, město?, ulice?, psč?, telefon*, fax*, e-mail*)> <!ELEMENT název (#PCDATA)> <!ELEMENT funkce (#PCDATA)>
Pro větší názornost přikládám i část zdrojového XML dokumentu se zvýrazněnou syntaxí tak, jak se zobrazuje v prohlížeči.
Pro přehlednost jednotlivých záznamů jsem vybral formátování dat do tabulek. Každý záznam (každá osoba) má vlastní tabulku s vnitřním členěním podle čtyř hlavních kategorií. Abych se vyhnul přemíře formátování v HTML, připojil jsem k HTML dokumentu list stylů (CSS), které obstarávají vizuální prezentaci (pozadí, barvy,... )
Protože je dále přiložen vlastní XSL soubor, který je částečně komentován, popíšu zde pouze stručně jeho činnost a detailněji rozvedu pouze některé části.
V první části se vyberou a podle abecedy setřídí všechny záznamy osob. Využívám zde toho, že jméno je uvedeno formou atributů kořenového elementu osoba a vypíšu celé jméno (i s tituly) jako záhlaví tabulky. Poté vygeneruji základní strukturu tabulky, do které se budou vkládat data z jednotlivých kategorií.
K osobním údajům se přidává také obrázek, který je pojmenován dle atributu ID (který je pro každou osobu jedinečný). Poté se postupně vypíšou všechny osobní údaje včetně příslušných dodatečných údajů (jednotky).
Rodinné údaje: Pokud má manžel, či manželka právě zpracovávané osoby vlastní záznam v adresáři (je uveden atribut ID), pak se vytvoří odkaz na vyhledání v seznamu. Část Děti se vypisuje pouze, pokud je v záznamu alespoň jedno dítě (využití funkce <xsl:if>).
Kategorie Bydliště a Zaměstnání mají podobnou strukturu, proto je popíšu jen zběžně a dohromady. V pravé části tabulky se nachází adresa (u zaměstnání doplněna názvem firmy a funkcí). V pravé části pak jsou uvedeny telefony a faxy (včetně předvoleb) a e-maily formou odkazu. Kliknutím na odkaz se otevře asociovaný poštovní program
<?xml version="1.0" encoding="windows-1250"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes" method="html" encoding="windows-1250"/> <!-- Vytvoreni zakladni kostry dokumentu --> <xsl:template match="/"> <html> <head> <title>Adresář</title> <link type="text/css" rel="StyleSheet" title="Zakladni styly" href="adresar.css" /> </head> <body> <xsl:for-each select="adresar/osoba"> <!-- Seradime podle prijmeni --> <xsl:sort select="@prijmeni" /> <xsl:sort select="@jmeno" /> <a name="{@id}"></a> <!-- Kazdy zaznam ve vlastni tabulce. Zahlavi je cele jmeno --> <table width="90%" border="1" align="center" cellpadding="3" cellspacing="1"> <caption> <xsl:value-of select="concat(@titul_pred,' ',@jmeno,' ',@prijmeni,' ',@titul_za)" /> </caption> <xsl:apply-templates/> </table> </xsl:for-each> </body> </html> </xsl:template> <!-- Pro kazdy oddil samostatne zahlavi --> <!-- Osobni udaje --> <xsl:template match="osobni"> <tr><th class="high">Osobní údaje</th> <!-- Odkaz na obrazek --> <td width="50%" rowspan="8" valign="center" align="center"><img src="images/{../@id}.jpg" alt="{../@jmeno} {../@prijmeni}" /></td> </tr> <!-- Vypis osobnich udaju. Kazdy na samostatne radce --> <xsl:for-each select="*"> <tr> <td> <b><xsl:value-of select="translate(name(),'_',' ')" /> <xsl:text>: </xsl:text></b> <xsl:value-of select="." /> <xsl:text> </xsl:text> <xsl:value-of select="@*" /> </td> </tr> </xsl:for-each> </xsl:template> <!-- Rodine udaje --> <xsl:template match="rodina"> <tr><th colspan="2" class="high">Rodiný stav: <xsl:value-of select="@stav" /></th></tr> <!-- Nejdriv testuje manzela/manzelku --> <xsl:apply-templates/> <!-- Potom testuje a vypisuje deti --> <xsl:if test="dítě"> <tr><td><b>děti:</b><ul> <xsl:for-each select="dítě"> <li> <xsl:value-of select="@jmeno" /> <xsl:text> </xsl:text> <xsl:value-of select="@prijmeni" /> </li> </xsl:for-each> </ul></td></tr> </xsl:if> </xsl:template> <xsl:template match="manžel-ka"> <tr> <td><b>manžel(ka):</b> <xsl:value-of select="@jmeno" /> <xsl:text> </xsl:text> <xsl:value-of select="@prijmeni" /> <!-- Pokud je maznel, ci manzelka v adresari, udelame nan odkaz --> <xsl:if test="@id"> <i>(<a href="#{@id}">Vyhledat</a>)</i></xsl:if> </td> </tr> </xsl:template> <!-- Bydliste --> <xsl:template match="bydliste"> <tr><th colspan="2" class="high">Bydliště</th></tr> <!-- Do leve bunky adresu --> <tr> <td class="ods"> <xsl:value-of select="ulice" /><br /> <xsl:value-of select="psč" /> <xsl:text>, </xsl:text> <xsl:value-of select="město" /><br /> <i>(okres <xsl:value-of select="okres" />)</i> </td> <!-- Do prave bunky prijde to ostatni: tel, fax, mail --> <td> <xsl:apply-templates /> </td> </tr> </xsl:template> <!-- Zamestnani je velmi podobne --> <xsl:template match="zamestnani"> <tr><th colspan="2" class="high">Zaměstnání</th></tr> <tr> <!-- Nejdriv nazev a funkce --> <td> <b><xsl:value-of select="název" /></b><br /><xsl:value-of select="funkce" /> </td> <!-- Pak telefon, fax, mail --> <td rowspan="2"> <xsl:apply-templates/> </td> </tr> <tr> <!-- Dolu pak adresu --> <td class="ods"> <xsl:value-of select="ulice" /><br /> <xsl:value-of select="psč" /> <xsl:text>, </xsl:text> <xsl:value-of select="město" /> </td> </tr> </xsl:template> <!-- Samostatna sablona vybira tel. cislo a predvolbu --> <xsl:template match="telefon | fax"> <b><xsl:value-of select="name()" />:</b> <xsl:text> </xsl:text> (<xsl:value-of select="@uto" />) <xsl:text> </xsl:text> <xsl:value-of select="." /><br /> </xsl:template> <!-- Samostatna sablona vybira e-amil --> <xsl:template match="e-mail"> <b>e-mail:</b> <a href="mailto:{.}"><xsl:value-of select="." /></a><br /> </xsl:template> <xsl:template match="text()"> <!-- Sablona pro vynechani std. textu --> </xsl:template> </xsl:stylesheet>
Výsledek transformace do HTML si můžete prohlédnout na následujícím obrázku.
Obsah
Pro výstup na tiskárnu je nejprve potřeba vytvořit soubor formátovacích objektů (FO), které říkají nejen co, ale i jak se bude tisknout. Pro tvorbu FO se použije (jak jinak :-)) XSLT. Protože struktura XML dokumentu je stále stejná a rozvržení pro tisk bude podobné, jako HTML dokument zobrazený na obrazovce, lze použít XSLT pro generování HTML a upravit výstup. Prostě místo HTML tagů napsat příslušné FO tagy. :-)
Protože struktura FO tagů je poněkud složitější, než příkazy pro generování HTML, nebudu zde vše detailně popisovat a funkci jednotlivých příkazů ponechám ke studiu zvídavému čtenáři. Na počátku je definice rozvržení stránky: okraje, záhlaví, zápatí, velikost stránky atp. Poté se vypisují všechny údaje ze zdrojového XML dokumentu v rozvržení (a pořadí) obdobném, jako při tvorbě HTML. Obdobně je řešeno i vložení obrázku, odkazy mezi osobami v adresáři a odkaz e-mailové adresy.
<?xml version="1.0" encoding="windows-1250"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0"> <!-- Vytvoreni zakladni kostry dokumentu --> <xsl:template match="/"> <fo:root> <fo:layout-master-set> <fo:simple-page-master page-height="297mm" page-width="210mm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2cm" margin-top="2.5cm" master-name="my-master"> <fo:region-body margin-bottom="15mm"/> <fo:region-after extent="10mm"/> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-name="my-master"> <fo:static-content flow-name="xsl-region-after"> <fo:block font-size="75%" text-align="center"> <!-- Číslo strany na každé stránce --> <xsl:text>Strana </xsl:text> <fo:page-number/> </fo:block> </fo:static-content> <fo:flow flow-name="xsl-region-body" font-family="Times Roman" font-size="12pt"> <fo:block> <xsl:for-each select="adresar/osoba"> <!-- Seradime podle prijmeni --> <xsl:sort select="@prijmeni"/> <xsl:sort select="@jmeno" /> <!-- Kazdy zaznam ve vlastni tabulce. Zahlavi je cele jmeno --> <fo:block font="bold 200% Helvetica" break-before="page" space-before="18pt" space-before.conditionality="discard" space-after="6pt" keep-with-next.within-column="always" keep-together.within-column="always" text-align="center" padding="3pt" background-color="silver" id="{@id}"> <xsl:value-of select="concat(@titul_pred,' ',@jmeno,' ',@prijmeni,' ',@titul_za)" /> </fo:block> <xsl:apply-templates/> </xsl:for-each> </fo:block> </fo:flow> </fo:page-sequence> </fo:root> </xsl:template> <!-- Pro kazdy oddil samostatne zahlavi --> <!-- Osobni udaje --> <xsl:template match="osobni"> <fo:block font-family="Helvetica" font-size="150%" margin-top="12pt">Osobní údaje</fo:block> <fo:table> <fo:table-body> <fo:table-cell starts-row="true"> <!-- Vypis osobnich udaju. Kazdy na samostatne radce --> <xsl:for-each select="*"> <fo:block padding-left="0.5cm"> <fo:inline color="navy"> <xsl:value-of select="translate(name(),'_',' ')" /> <xsl:text>: </xsl:text> </fo:inline> <xsl:value-of select="." /> <xsl:text> </xsl:text> <xsl:value-of select="@*" /> </fo:block> </xsl:for-each> </fo:table-cell> <fo:table-cell ends-row="true"> <fo:block text-align="center"> <!-- Odkaz na obrazek --> <fo:external-graphic src="url('images/{../@id}.jpg')"/> </fo:block> </fo:table-cell> </fo:table-body> </fo:table> </xsl:template> <!-- Rodine udaje --> <xsl:template match="rodina"> <fo:block font-family="Helvetica" font-size="150%" margin-top="12pt">Rodiný stav: <xsl:value-of select="@stav" /></fo:block> <!-- Nejdriv testuje manzela/manzelku --> <xsl:apply-templates/> <!-- Potom testuje a vypisuje deti --> <xsl:if test="dítě"> <fo:block color="navy" padding-left="0.5cm">děti:</fo:block> <fo:list-block margin-top="-14pt" space-after="6pt" padding-left="1.5cm"> <xsl:for-each select="dítě"> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block font-family="ZapfDingbats">➡</fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:block> <xsl:value-of select="@jmeno" /> <xsl:text> </xsl:text> <xsl:value-of select="@prijmeni" /> </fo:block> </fo:list-item-body> </fo:list-item> </xsl:for-each> </fo:list-block> </xsl:if> </xsl:template> <xsl:template match="manžel-ka"> <fo:block padding-left="0.5cm"> <xsl:choose> <xsl:when test="@id"> <!-- Pokud je uvedeno Id, vytvori se odkaz --> <fo:basic-link color="navy" internal-destination="{@id}" text-decoration="underline">manžel(ka):</fo:basic-link> </xsl:when> <xsl:otherwise> <!-- Jinak se napise jenom jmeno --> <fo:inline color="navy">manžel(ka):</fo:inline> </xsl:otherwise> </xsl:choose> <xsl:text> </xsl:text> <xsl:value-of select="@jmeno" /> <xsl:text> </xsl:text> <xsl:value-of select="@prijmeni" /> </fo:block> </xsl:template> <!-- Bydliste --> <xsl:template match="bydliste"> <fo:block font-family="Helvetica" font-size="150%" margin-top="12pt">Bydliště</fo:block> <fo:table> <fo:table-body> <fo:table-cell starts-row="true"> <!-- Do leve bunky adresu --> <fo:block padding-left="0.5cm"> <xsl:value-of select="ulice" /> </fo:block> <fo:block padding-left="0.5cm"> <xsl:value-of select="psč" /> <xsl:text>, </xsl:text> <xsl:value-of select="město" /> </fo:block> <fo:block padding-left="0.5cm" font-size="85%"> (okres <xsl:value-of select="okres" />) </fo:block> </fo:table-cell> <fo:table-cell ends-row="true"> <!-- Do prave bunky prijde to ostatni: tel, fax, mail --> <fo:block padding-left="0.5cm"> <xsl:text> </xsl:text> </fo:block> <xsl:apply-templates /> </fo:table-cell> </fo:table-body> </fo:table> </xsl:template> <!-- Zamestnani je velmi podobne --> <xsl:template match="zamestnani"> <fo:block font-family="Helvetica" font-size="150%" margin-top="12pt">Zaměstnání</fo:block> <!-- Nejdriv nazev a funkce --> <fo:block padding-left="0.5cm"> <xsl:value-of select="název" /> </fo:block> <fo:block padding-left="1cm" font-size="85%"> <xsl:value-of select="funkce" /> </fo:block> <fo:table> <fo:table-body> <fo:table-cell starts-row="true"> <!-- Dolu doleva pak adresu --> <fo:block padding-left="0.5cm"> <xsl:value-of select="ulice" /> </fo:block> <fo:block padding-left="0.5cm"> <xsl:value-of select="psč" /> <xsl:text>, </xsl:text> <xsl:value-of select="město" /> </fo:block> </fo:table-cell> <fo:table-cell ends-row="true"> <!-- Do prave bunky prijde to ostatni: tel, fax, mail --> <fo:block padding-left="0.5cm"> <xsl:text> </xsl:text> </fo:block> <xsl:apply-templates /> </fo:table-cell> </fo:table-body> </fo:table> </xsl:template> <!-- Samostatna sablona vybira tel. cislo a predvolbu --> <xsl:template match="telefon | fax"> <fo:block padding-left="0.5cm"> <fo:inline color="navy"> <xsl:value-of select="name()" />: </fo:inline> <xsl:text> </xsl:text> (<xsl:value-of select="@uto" />) <xsl:text> </xsl:text> <xsl:value-of select="." /> </fo:block> </xsl:template> <!-- Samostatna sablona vybira e-amil --> <xsl:template match="e-mail"> <fo:block padding-left="0.5cm"> <fo:inline color="navy"> e-mail: </fo:inline> <fo:basic-link external-destination="url(mailto:{.})" color="#0000C0" text-decoration="underline"><xsl:value-of select="." /></fo:basic-link> </fo:block> </xsl:template> <xsl:template match="text()"> <!-- Sablona pro vynechani std. textu --> </xsl:template> </xsl:stylesheet>
Poté, co získáme soubor s formátovacími objekty, můžeme tento libovolně upravovat, nebo (což je nepochybně větší zábava) z něj vygenerovat soubor vhodný pro tisk. Použil jsem omezenou verzi programu XEP a vygeneroval výsledný PDF soubor.
Program XEP má bohužel problémy s Češtinou, tudíž některé znaky prostě vynechává, což poněkud kazí výsledný dojem. Na druhou stranu XEP i v omezené demoverzi zvládá dobře standardy vznikající FO včetně tabulek, vícesloupcové sazby apod. Na rozdíl od plné verze demo neumí vytvořit PostScript verzi, do každé stránky vkládá odkaz na svou domovskou stránku a od jedenácté stránky je každá lichá stránka prázdná. :-( Proto příště nebudu hloupý a pořídím si zdarma a legálně nějaký volně šířitelný FO parser. Ale ta podpora!
Jak to vše dopadlo je zřejmé z následujícího obrázku.