Toto je pokus o kultivované programování transformačního stylu. Co kultivované programování (literate programming) znamená? Je to přístup, kdy jeden zdrojový soubor obsahuje současně programový kód i jeho podrobnou dokumentaci. Nějaký nástroj z něj pak vybere programový kód, aby mohl být spuštěn, a jiný nástroj vytvoří krásně vytištěnou dokumentaci. Existují systémy WEB (nezaměňovat za World Wide Web) pro většinu programovacích jazyků a balíky ltxdoc a přidružený docstrip pro LaTeX. Všechny tyto nástroje lze najít na CTAN.
Zde je nutno připojit jazykovou poznámku. Někteří překladatelé anglických textů zaměňují slovo literate, které znamená gramotný, vzdělaný, kultivovaný, se slovem literary, jež znamená literární. Překlad literární programování je tedy zcestný. Z výše uvedených významů se mi v tomto kontextu nejvíce líbí kultivované programování. Opačné slovo, illiterate, znamená pouze negramotný. Neříkejte proto těm, kdo používají tradiční programátorský styl, že jejich přístup je illiterate. Nemají to rádi.
XSLT jako takové je vhodný nástroj pro kultivované
programování. Několik takových systémů lze najít na webu. Neznám však
žádný nástroj určený ke kultivovanému programování stylů XSLT. Přestože
noweb nebo jiný konfigurovatelný nástroj by mohl
být použit pro kultivované programování transformačního stylu, rozhodl
jsem se zůstat u řešení vycházejícího čistě z XML. Snažil jsem se, aby
zdrojový kód i styly byly co nejjednodušší. Proto atribut
test
v elementech <xsl:if>
a <xsl:when>
zobrazuje "<
" a ">
", zatímco skutečný obsah je
<
a >
. Testy též obsahují
uvozovky uvnitř uvozovek, což je nepřípustné. Ve skutečném kódu se
střídají uvozovky s apostrofy.
Obsah
Stránky mají anglickou i českou verzi. Jejich obsah by měl být týž. Není však jednoduché udržovat konzistentně dvě sady stránek. Je vhodné, když obě jazykové mutace mohou přebývat v jediném souboru. Původně měly stránky dva sloupce s anglickým textem v levém a českým textem v pravém sloupci. Společné části byly zobrazeny v jednom sloupci, který přerušoval dvousloupcovou sazbu.
Dvousloupcová prezentace má své nevýhody. Čtenář je rušen přítomností druhého jazyka. Kromě toho mají některé části textu smysl pouze v jedné jazykové verzi. Vyvážení sloupců je tím narušeno. Bylo tedy rozhodnuto, že nové uspořádání bude mít samostatné české a anglické stránky, které budou generovány ze společného zdroje zapsaného v XML.
První otázkou je, jaký vstupní formát máme použít. Nabízí se standardní DocBook. Tento formát je však dosti komplikovaný. Poskytuje nepřeberné možnosti, které však nevyužijeme, protože text má sloužit výhradně pro webové stránky a nepředpokládá se žádné jiné zpracování. Důležitý je zejména vzhled stránek. Sice lze stáhnout nástroj, který transformuje dokument používající DocBook do HTML, ale úpravy těchto stylů by byly dosti pracné. Proto byl tento formát odmítnut.
Nejstarší verze stránek byla psána přímo v HTML. Jako nejpřirozenější se ukázalo obohacení HTML o několik elementů. Tak vznikla druhá verze v době, kdy jsem XML a XSLT ještě moc neuměl a na tomto projektu jsem se je učil. Nyní jsem dospěl do stavu, kdy si uvědomuji, že řadu věcí jsem dělal komplikovaně a nekoncepčně. Opět jsem si vytýčil smělý plán, kdy se ukázalo, že umím méně, než jsem si myslel. Opět jsem se při psaní tohoto dokumentu naučil řadu novinek. Myšlenka obohacení HTML o několik elementů však stále přežívá.
Již v první verzi, kterou jsem generoval z XML, jsem chtěl
rozšiřující elementy vzít z vlastního jmenného prostoru. Nelíbilo se mi,
že hlavní element <html>
pak obsahoval deklaraci tohoto
jmenného prostoru, přestože se do stránky žádný z mých elementů
nedostal. Tehdy jsem nevěděl, že prefix mého jmenného souboru stačí
uvést v elementu <xsl:stylesheet>
v atributu
exclude-result-prefixes
. Nyní jsem znovu použití jmenného
prostoru uvážil. Dospěl jsem k závěru, že vstupní formát je definován
mými vlastními tagy, které se záměrně v mnoha případech shodují s elementy HTML. Tím jsem si své řešení ospravedlnil.
Při formátování stránky lze postupovat dvěma krajními způsoby:
První metoda vede k nepřehlednému kódu. Sice by to nemuselo vadit, protože stránky generujeme z XML, ale formátovacími značkami by se zaneřádil transformační styl. Druhá metoda má následující nevýhody:
Z obecného hlediska tedy musíme formátování kaskádovým stylem odmítnout. Na druhé straně nám kaskádové styly nabízejí užitečné možnosti, z nichž některé přímo v HTML nejsou. Můžeme tedy udělat kompromis: základní formátování, které musí být zachováno ve všech prohlížečích, řešit přímo prostředky HTML, a kaskádový styl využít pro zkrášlení stránek. Takto tvořené stránky zobrazí dokonce i Lynx.
V budoucnu možná i kaskádový styl bude programován kultivovaně. Nyní je psán přímo. Můžete si prohlédnout jeho textovou verzi.
Pro zpracování těchto stránek byl vybrán procesor Saxon, protože patří mezi nejrychlejší a je napsán v Javě, takže funguje ve všech operačních systémech. Transfornační styl byl původně psán pro verzi 6.5.2, nyní byl modifikován tak, aby fungoval s verzí 8, která používá XSLT 2.0. V popisu zůstává několik poznámek, jak to bylo uděláno pro XSLT 1.0.
Smyslem kultivovaného programování je udržet v synchronizaci
vlastní kód a jeho dokumentaci. Proto je vše v jednom souboru. Chceme-li
stejnou metodu využít při programování transformačního stylu, měli
bychom jej tvořit tak, aby puštěn sám na sebe vytvořil vlastní
dokumentaci. V principu by to šlo, ale zdrojový soubor by byl
komplikovaný a snadno by se v něm nadělaly chyby. Proto si vytvoříme
maličký styl, který z XML souboru webmake.xml
vytvoří styl
pro transformaci do HTML. Tento transformační styl pak použijeme nejen
na tvorbu vlastních stránek, ale i na vytvoření dokumentace stylu.
Transformační styl je také XML soubor, proto jej můžeme vytvořit transformací z jiného XML souboru. Abychom to mohli provést, musíme použít drobný trik. Nestačí jen deklarace jmenného prostoru pro XSLT, musíme též deklarovat další prostor, který později použijeme jako alias:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslt="namespace-alias" version="2.0" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"> |
Prefix xsl:
je určen jmennému prostoru XSLT,
prefix xslt:
je přiřazen libovolnému jmennému prostoru. V elementu <xsl:namespace-alias>
určíme, že elementy s prefixem xslt:
budou mít ve výsledném souboru prefix
xsl:
. Tak můžeme do šablony zapsat transformační
elementy, které se neprovedou, ale zkopírují do výsledného souboru. Kód
vysvětlíme dodatečně.
<xsl:namespace-alias stylesheet-prefix="xslt" result-prefix="xsl"/> <xsl:output method="xml" indent="yes" encoding="utf-8" saxon:character-representation="native"/> <xsl:strip-space elements="*"/> <xsl:preserve-space elements="xslt:text xsl:text"/> <xsl:param name="mode"/> <xsl:template match="/"> <xslt:stylesheet version="2.0" saxon:trace="no" exclude-result-prefixes="zw xs" extension-element-prefixes="saxon"> <xsl:apply-templates select="//zw-pre"/> </xslt:stylesheet> </xsl:template> <xsl:template match="zw-pre"> <xsl:if test="@mode=$mode or (not($mode) and not(@mode))"> <xsl:apply-templates/> </xsl:if> </xsl:template> <xsl:template match="zw-hide"> <xsl:apply-templates/> </xsl:template> <xsl:template match="*|@*"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template match="text()"> <xsl:value-of select="." disable-output-escaping="yes"/> </xsl:template> <xsl:template match="processing-instruction()"> <xsl:processing-instruction name="{name()}"><xsl:value-of select="."/></xsl:processing-instruction> </xsl:template> <xsl:template match="zw-text"> <xslt:text> <xsl:value-of select="."/> </xslt:text> </xsl:template> |
Výstupním formátem je vždy XML kódovaný v UTF-8. Ignorujeme
mezery ve všech elementech s výjimkou <xsl:text>
a <xslt:text>
. Vlastní šablona vybírá jen obsah
elementů <zw-pre>
bez ohledu na to, jak hluboko jsou
vnořeny. Z jednoho zdroje budeme generovat několik transformačních
stylů. Odlišíme je globálním parametrem mode
a stejnojmenným atributem elementu <zw-pre>
. V souboru
webmake.xml
, z něhož výsledný styl generujeme, však musí
elementy patřit do jmenného prostoru XSLT, jinak by funkční styl
nevznikl. Pokud bychom vytvářeli elementy dynamicky pomocí
<xsl:element>
, museli bychom v šablonách použít prefix
xsl:
, nikoliv alias xslt:
.
Element <zw-hide>
slouží k tomu, abychom
utajili obsah, který z bezpečnostních důvodů nechceme zveřejnit. Zde
musíme jeho obsah zpracovat, ve výpisu kódu jej nahradíme alternativním
textem, jak bude uvedeno později.
Při zpracování elementu <xsl:text>
a instrukcí pro zpracování potřebujeme zachovat skutečné znaky a nenahrazovat je entitami. Proto použijeme atribut
disable-output-escaping='yes'
.
Element <zw-text>
bude do výsledného stylu
transformován jako <xsl:text>
a musí v něm být použity
znakové entity.
Všimněte si, že v elementu
<xslt:stylesheet>
nemusíme deklarovat jmenný prostor
pro XSLT, protože jej procesor XSLT doplní automaticky. Přesněji řečeno,
budou deklarovány dva identické jmenné prostory pro oba prefixy,
xslt:
i xsl:
. Nemusíme uvádět ani deklaraci
jmenného prostoru pro Saxon, protože XSLT procesor jej doplní všude, kde
je třeba. A v tom je kámen úrazu. Tento jmenný prostor je třeba v elementu <xsl:output>
, protože obsahuje atribut
saxon:character-representation
. V elementu
<xslt:stylesheet>
však formálně nutný není. Atribut
extension-element-prefixes
však vyžaduje, aby prefixy
byly přiřazeny platným jmenným prostorům. Proto přidáváme atribut
saxon:trace='no'
, přestože je to default. Je to pouze
donucovací prostředek pro procesor XSLT, aby vložil deklaraci jmenného
prostoru.
Zde je nutno upozornit, že v následujících šablonách budeme používat rozšíření procesoru Saxon. S jiným procesorem XSLT nebudou šablony fungovat.
Na konci souboru je samozřejmě uzavírací tag.
</xsl:stylesheet> |
V předchozí části jsme si vygenerovali transformační styl z dokumentu, který právě čtete. Styl je dosti rozsáhlý. Jeho popis je tedy rozčleněn do kapitol.
Při transformaci dokumentu bude nutno zpracovávat některé
elementy opakovaně různým způsobem. Využijeme přitom atribut
mode
. XSLT 1.0 povoluje zpracování šablony
pouze v jednom režimu. Pokud chceme stejnou činnost provádět v několika režimech, musíme si vytvořit pojmenovanou šablonu
pro jeden z režimů takto:
<xsl:template match="something" mode="foo" name="something-foo"> <xsl:text>Do something here</xsl:text> </xsl:template> |
V šablonách pro další režim pouze voláme jménem předchozí šablonu:
<xsl:template match="something" mode="bar"> <xsl:call-template name="something-foo"/> </xsl:template> |
XSLT 2.0 umožňuje použití šablony ve více režimech. Uvedený příklad lze tedy jednoduše přepsat:
<xsl:template match="something" mode="foo bar"> <xsl:text>Do something here</xsl:text> </xsl:template> |
Podrobnou syntaxi najdete ve specifikaci na http://www.w3.org/TR/xslt20/#modes.
Jednou z hlavních úloh je rozdělení obsahu podle jazyka.
Nabízí se využití standardního atributu xml:lang
a funkce
lang()
. Tento přístup má však výhodu, pokud jsou velké
části dokumentu v jednom jazyku. My chceme prolínat malé kousky textu
v různých jazycích a navíc chceme mít obecné části, které patří do
všech jazykových verzí. Využití standardu by nám tedy způsobovalo
těžkosti.
Pro výběr obsahu podle jazyka byl tedy vytvořen element
<zw>
. Původně byl připraven pouze pro dvojjazyčné
stránky. Obsahoval jednoduchou podmínku a elementy nemohly být
vnořeny.
Zastaralý kód |
---|
<xsl:template match="zw" name="zw"> <xsl:if test="@lang=$DocumentLanguage"> <xsl:apply-templates/> </xsl:if> </xsl:template> |
Nová šablona pro zpracování elementu <zw>
je multilinguální a umožňuje vnoření. Element má dva atributy:
lang
a notlang
. Oba mohou obsahovat seznam
jazykových kódů oddělených mezerami nebo čárkami. Atribut
notlang
obsahuje seznam jazyků, v nichž se obsah
objevit nemá, atribut lang
určuje jazyky, v nichž se
obsah elementu objeví. Nemá smysl použít oba atributy současně, ale
pokud se to stane, má atribut notlang
vyšší prioritu.
Není-li obsah elementu vkládán do výstupu, zpracují se pouze vnořené
elementy <zw>
. Pokud není uveden žádný atribut, obsah
elementu <zw>
funguje jako komentář, který smí
obsahovat jakékoliv správně formátované značkování včetně komentářů.
Šablona musí fungovat stejně ve všech režimech.
<xsl:template match="zw" mode="#all"> <xsl:choose> <xsl:when test="not(@lang) and not(@notlang)"/> <xsl:when test="contains(@notlang, $DocumentLanguage)"> <xsl:apply-templates select="zw" mode="#current"/> </xsl:when> <xsl:when test="contains(@lang, $DocumentLanguage) or not(@lang)"> <xsl:apply-templates mode="#current"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="zw" mode="#current"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Jazykovou variantu bude prohlížeč WWW vybírat metodou sjednání obsahu (content negotiation) implementovanou v PHP
skriptu. Musíme tedy ke každému zdrojovému dokumentu vytvořit
indexový skript. Provedeme to jednoduchým transformačním stylem, který
zpracovává pouze kořenový uzel. Všimněte si, že programový kód v PHP
generujeme jako text. Komentář definuje místo, kam ISP vloží reklamu.
Je až za koncem programového kódu, aby nebyla na stránce dvakrát. Jak
poznáte později, skutečná reklama bude vložena při zpracování elementu
<document>
.
mode = index |
---|
<xsl:output method="text" indent="yes" encoding="utf-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <zw-text><?php require 'engine.php'; if ($inc) { include $fn; } else virtual($fn); exit; ?> <!--WZ-REKLAMA-1.0--> </zw-text> </xsl:template> |
Výstupní metoda je html
a požadujeme
odsazení. Pro výběr jazyka musíme znát jeho kód, který zadáme na příkazovém řádku.
<xsl:output method="html" indent="yes" encoding="utf-8"/> <xsl:param name="DocumentLanguage"/> |
V předchozí verzi jsme používali dva maličké styly, které
pouze nastavily parametry daného jazyka a načetly hlavní soubor pomocí
elementu
<xsl:import href="xml2html.xsl"/>
.
XML dokument obsahuje řadu mezer, které by rozházely
formátování výsledného dokumentu, zejména výpis kódu transformačního
stylu. Proto budeme všechny mezery ignorovat. Musíme však zachovat
mezery v elementu <xsl:text>
a některých dalších
textových elementech.
<xsl:strip-space elements="*"/> <xsl:preserve-space elements="xsl:text p i b li dt dd zw pre"/> |
Při dalším zpracování budeme potřebovat jméno dokumentu
bez adresářů a bez přípony. Víme, že zdrojový dokument má vždy příponu
.xml
, proto si do globální proměnné
basename
uložíme vše mezi posledním lomítkem a touto
příponou.
<xsl:variable name="basename"> <xsl:analyze-string select="saxon:systemId()" regex="[^/]+\.xml$"> <xsl:matching-substring> <xsl:value-of select="."/> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Verze pro XSLT 1.0 obsahovala:
Zastaralý kód |
---|
<xsl:variable name="basename" select="substring-before(saxon:tokenize(saxon:systemId(),'/')[last()],'.xml')"/> |
Na všech stránkách se budeme odkazovat na hlavní stránku a kaskádový styl. Aby stránky nebyly závislé na konkrétním umístění
a daly se testovat lokálně, musí být URL relativní. Musíme tedy určit
počet zpětných kroků. Základní adresář s XML dokumenty je
.../xml/
. Zjistíme si tedy systémový identifikátor
zpracovávaného souboru a vezmeme vše za tímto řetězcem. Rozdělíme ji
na části podle lomítek. Adresáře pak nahradíme dvěma tečkami a výsledek si schováme pro další použití v globální proměnné
backdir
.
<xsl:variable name="backdir"> <xsl:analyze-string select="substring-after(saxon:systemId(), "/xml/")" regex="[^/]+/"> <xsl:matching-substring> <xsl:text>../</xsl:text> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Zde je kód použitý ve staré verzi:
Zastaralý kód |
---|
<xsl:variable name="backdir"> <xsl:variable name="ntoks" select="count(saxon:tokenize(substring-after(saxon:systemId(), '/xml/'), '/'))"/> <xsl:if test="$ntoks > 1"> <xsl:for-each select="saxon:range(2, $ntoks)"> <xsl:text>../</xsl:text> </xsl:for-each> </xsl:if> </xsl:variable> |
Odkaz na domovskou stránku je buď obsah proměnné
$backdir
, nebo ./
.
<xsl:variable name="home"> <xsl:choose> <xsl:when test="$backdir = ''"> <xsl:text>./</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="$backdir"/> </xsl:otherwise> </xsl:choose> </xsl:variable> |
Nakonec si do globálních proměnných uložíme text "Hlavní
stránka" v příslušném jazyce. Přípona souboru je vždy
.php
.
<xsl:variable name="mainpage"> <xsl:choose> <xsl:when test="$DocumentLanguage = 'cs'"> <xsl:text>Hlavní stránka</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage = 'hi'"> <xsl:text>मुख्य पृष्ठ</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Main page</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="htmlext"> <xsl:text>.php</xsl:text> </xsl:variable> |
Nyní již můžeme zpracovat dokument. V šabloně použijeme
jednoduchý trik. Kořenovým elementem v našem formátu musí být
<document>
. Ověření, že dokument vyhovuje této
podmínce, je velmi snadné. V šabloně určíme, že se mají použít další
šablony na element <document>
. Pokud je tedy kořenový
element jiný, bude výsledná stránka obsahovat pouze prázdný element
<html>
, čehož si snadno všimneme. Instrukce pro
zpracování, která se generuje na počátku souboru, slouží k načtení
kódu pro výběr jazyka metodou
popsanou v předchozí podkapitole.
<xsl:template match="/"> <xsl:processing-instruction name="php"> if (!function_exists('FindLanguage')) { require 'functions.php'; EmitHeader(basename($_SERVER['SCRIPT_NAME'])); } Expires(); ?</xsl:processing-instruction> <html> <xsl:apply-templates select="document"/> </html> </xsl:template> |
Z některých stránek se generuje též offline manuál pro distribuční balíčky. Část obsahu má smysl pouze na webových stránkách, jiná část pouze v manuálu. Vytvoříme proto dvě šablony umožńující zápis alternativ. Předpokládáme, že v transformačním stylu pro manuál budou předefinovány.
<xsl:template match="zw-online"> <xsl:apply-templates/> </xsl:template> <xsl:template match="zw-offline"/> |
Tyto elementy nesmějí obsahovat elementy
<section>
a <title>
, přímo ani
nepřímo. V případě elementu <title>
je řešení snadné,
přepínače <zw-online>
a <zw-offline>
vložíme dovnitř. V elementu <section>
využijeme
atributu role
. Podrobněji to bude vysvětleno v kapitolách
"Kapitoly a podkapitoly" a "Šablony pro obsah". Pro ten účel si
připravíme proměnnou obsahující název role, již máme ignorovat.
<xsl:variable name="ignore-role"> offline</xsl:variable> |
XPath má nástroj na zjištění aktuálního data a času, ale neumí zjistit datum a čas modifikace souboru. Ve starší verzi tgransformačního stylu jsme si pomohliexterním programem, který prošel strom zdrojových dokumentů a vytvořil pomocný soubor s požadovanými informacemi. Jméno externího souboru bylo zadáno jako parametr a jeho dokumentní uzel byl uložen do proměnné.
Zastaralý kód |
---|
<xsl:param name="FileInfo" required="yes"/> <xsl:variable name="filenode" select="document($FileInfo)/zw-fileinfo"/> |
Výše zmíněný soubor obsahoval pouze prázdné elementy
<zw-file>
, které měly dva atributy. Atribut
name
obsahoval jméno souboru, atribut
lastmod
obsahoval datum a čas poslední modifikace. Datum
bylo od času odděleno nezlomitelnou mezerou. Pro efektivnější použití
byl definován klíč.
Zastaralý kód |
---|
<xsl:key name="flastmod" match="zw-file" use="@name"/> |
Tato šablona vracela datum a čas modifikace souboru zadaného jako parametr. Defaultně vracela datum a čas souboru obsahujícího kontextový uzel.
Zastaralý kód |
---|
<xsl:template name="flastmod"> <xsl:param name="file"> <xsl:value-of select="saxon:system-id()"/> </xsl:param> <xsl:for-each select="$filenode"> <xsl:value-of select="key("flastmod", $file)/@lastmod"/> </xsl:for-each> </xsl:template> |
Element <zw-datetime>
vytvoří datum a čas
modifikace souboru s využitím balíčku
s rozšiřující funkcí pro Saxon. Jméno souboru se vezme buď z atributu file
, nebo, v případě XML souborů, z atributu
xmlfile
. Pokud není zadán žádný z těchto atributů,
vytvoří se datum a čas modifikace souboru obsahujícího kontextový
uzel. Šablonu si pojmenujeme, abychom ji mohli použít později.
<xsl:template match="zw-datetime" name="zw-datetime"> <xsl:choose use-when="function-available("zw:last-modified")"> <xsl:when test="@file"> <xsl:value-of select="zw:file-timestamp(@file)"/> </xsl:when> <xsl:when test="@xmlfile"> <xsl:value-of select="zw:last-modified(document(@xmlfile))"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="zw:last-modified()"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Datum poslední modifikace získáme odříznutím času z údaje zjištěného předchozí šablonou.
<xsl:template match="zw-date"> <xsl:variable name="datetime"> <xsl:call-template name="zw-datetime"/> </xsl:variable> <xsl:value-of select="substring-before($datetime, "T")"/> </xsl:template> |
Při zpracování elementu <document>
provádíme tři činnosti. Nejprve v hlavičce vytvoříme název stránky,
který bude zobrazen v prohlížeči. V druhém kroku vytvoříme odkaz na
kaskádový styl. Nakonec zpracujeme tělo dokumentu. Každou z těchto
částí provede samostatná šablona, kterou zavoláme elementem
<xsl:call-template>
. Děláme to čistě z důvodů
přehlednosti, neboť každá z těchto šablon je použita pouze na jednom
místě.
<xsl:template match="document"> <head> <meta name="google-site-verification" content="pQX0Li9frYMCGMBkoPTHoG3lgeK_Gx9i-BO8-jcjn18"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="http-equiv" content="{concat('Content-Language: ', $DocumentLanguage)}"/> <xsl:call-template name="doc-title"/> <xsl:if test="@css"> <xsl:call-template name="doc-css"/> </xsl:if> <xsl:if test="@redirect"> <meta http-equiv="refresh" content="{concat('1; ', @redirect)}"/> </xsl:if> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-98691706-1', 'auto'); ga('send', 'pageview'); </script> </head> <xsl:call-template name="doc-body"/> </xsl:template> |
Nadpis, který má prohlížeč zobrazit na liště, vezmeme z elementu <title>
. Tento element však primárně slouží
jako nadpis stránky a pro zobrazení na liště nemusí být vhodný. Pokud
tedy dokument obsahuje element <titlebar>
, dáme mu
přednost. Všimněte si atributu mode='titlebar'
v elementu
<xsl:apply-templates>
. Jeho význam si vysvělíme
později.
Zde je také vhodné místo pro vložení dalšího obsahu do
elementu <head>
. Přidáme obsah všech elementů tohoto
jména.
<xsl:template name="doc-title"> <title> <xsl:choose> <xsl:when test="count(titlebar)>0"> <xsl:apply-templates select="titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </title> <xsl:apply-templates select="head" mode="title"/> </xsl:template> |
Zpracování těla dokumentu je snadné. Nejprve z elementu
<document>
zkopírujeme všechny atributy s výjimkou
top
a css
. Pak zobrazíme nadpis. Přidáme k němu návěští top
, abychom později mohli vytvářet odkazy
na začátek stránky. Šablonu pro zpracování elementu
<title>
voláme s atributem mode='title'
.
Poté aplikujeme šablony na všechny elementy. Musíme jim však předat
parametr hdr
, který bude využit v šabloně pro element
<section>
. Pokud má atribut top
kladnou hodnotu, zavoláme ještě šablonu pro
vytvoření odkazu na začátek stránky, kterou si uvedeme později.
Na konci stránky vytvoříme standardní patičku.
<xsl:template name="doc-body"> <body> <xsl:copy-of select="@* except (@top|@css)"/> <h1 id="top"> <xsl:apply-templates select="title" mode="title"/> </h1> <xsl:apply-templates> <xsl:with-param name="hdr" select="2"/> </xsl:apply-templates> <xsl:if test="@top > 0"> <xsl:call-template name="top-link"/> </xsl:if> <xsl:call-template name="footer"/> </body> </xsl:template> |
Nejprve vynecháme trochu prázdného místa, aby nám případný
odkaz na začátek stránky nekolidoval se zápatím. Pak si ověříme, zda
náhodou nejsme na hlavní stránce. V takovém případě bychom
nezobrazovali odkaz na hlavní stránku, ale jen vodorovnou linku.
Nejsme-li v hlavním adresáři, zavoláme šablonu, která přidá odkazy
definované v elementech <zw-a>
. Potom vložíme odkaz
na hlavní dokument daného adresáře. Jeho název vezmeme z elementu
<title>
zpracovaného v režimu
titlebar
. Dále zobrazíme copyright a datum poslední
modifikace získané pomocí dříve definované šablony. Až nakonec uvedeme odkaz
na ostatní jazykové verze, aby to nepletlo indexovací roboty.
Kaskádový styl jej přesune na začátek stránky. Komentář
<!--WZ-REKLAMA-1.0-->
určuje místo, kam server automaticky vloží reklamní
proužek. Pod ním se může vyskytnout další reklama načítaná z dokumentu, jehož URL je určeno v atributu reklama
.
<xsl:template name="footer"> <br/> <br/> <br/> <xsl:variable name="urlbase" select="concat($backdir, $basename)"/> <xsl:choose> <xsl:when test="$urlbase = 'index.xml'"> <hr/> </xsl:when> <xsl:otherwise> <div class="links"> <xsl:call-template name="other-links"/> <xsl:if test="starts-with($urlbase, '../') and $basename != 'index.xml'"> <a href="./"> <xsl:attribute name="title"> <xsl:choose> <xsl:when test="count(document('index.xml', /)/document/titlebar) > 0"> <xsl:apply-templates select="document("index.xml", /)/document/titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="document("index.xml", /)/document/title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="document("index.xml", /)/document/title" mode="titlebar"/> </a> <br/> </xsl:if> <a title="{$mainpage}" href="{$home}"> <xsl:value-of select="$mainpage"/> </a> </div> </xsl:otherwise> </xsl:choose> <div id="copyright"> <xsl:text>© Z. Wagner - Ice Bear Soft, </xsl:text> <xsl:value-of select="zw:last-modified()" use-when="function-available("zw:last-modified")"/> </div> <xsl:text> </xsl:text> <xsl:processing-instruction name="php"> LangLinks(); Counter(); ?</xsl:processing-instruction> <xsl:comment>WZ-REKLAMA-1.0</xsl:comment> <xsl:if test="@reklama"> <hr/> <xsl:apply-templates select="document(@reklama)" mode="include"/> </xsl:if> </xsl:template> |
Tato šablona vloží obsah všech elementů
<zw-a>
zpracovaných v režimu footer
.
<xsl:template name="other-links"> <xsl:apply-templates select="//zw-a" mode="footer"/> </xsl:template> |
Element <zw-a>
má téměř stejnou syntaxi
jako element <a>
. Jedinou výjimkou je, že atribut
title
elementu <a>
může být v elementu <zw-a>
zadán v elementu
<title>
, abychom mohli mít více jazykových verzí. V režimu footer
tedy element <zw-a>
nahradíme elementem <a>
následovaným řádkovým
zlomem, v defaultním režimu jej budeme ignorovat. Pokud je však
obsah elementu <zw-a>
prázdný, naplníme jej
společně s atributem title
z názvu odkazovaného
dokumentu. Při vkládání odkazu bereme ohled na jazykovou verzi podle atributů
lang
a notlang
.
<xsl:template match="zw-a" mode="footer"> <xsl:choose> <xsl:when test="@notlang eq $DocumentLanguage"/> <xsl:when test="@lang ne '' and @lang ne $DocumentLanguage"/> <xsl:otherwise> <xsl:call-template name="zw-a"/> <br/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="zw-a"> <a> <xsl:copy-of select="@*"/> <xsl:call-template name="zw-a-content"/> </a> </xsl:template> <xsl:template match="zw-a"/> <xsl:template name="zw-a-content"> <xsl:choose> <xsl:when test="count(*) > 0 or string-length(.) > 0"> <xsl:if test="title"> <xsl:attribute name="title"> <xsl:apply-templates select="title" mode="titlebar"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="*|text() except title"/> </xsl:when> <xsl:otherwise> <xsl:choose> <xsl:when test="contains(@href, "#")"> <xsl:variable name="xml" as="xs:string" select="if (starts-with(@href, "#")) then @href else replace(replace(@href, "/#", "/index.php#"), "\.php#", ".xml#")"/> <xsl:variable name="root" select="if (starts-with(@href, "#")) then (/) else document(substring-before($xml, "#"), /)"/> <xsl:variable name="title" as="element()" select="$root//section[@label=substring-after($xml,"#")]/title"/> <xsl:attribute name="title"> <xsl:apply-templates select="$title" mode="titlebar"/> </xsl:attribute> <xsl:apply-templates select="$title" mode="title"/> </xsl:when> <xsl:otherwise> <xsl:variable name="xml" as="xs:string" select="replace(replace(@href, "/$", "/index.php"), "\.php$", ".xml")"/> <xsl:attribute name="title"> <xsl:choose> <xsl:when test="count(document($xml, /)/document/titlebar) > 0"> <xsl:apply-templates select="document($xml, /)/document/titlebar" mode="titlebar"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="document($xml, /)/document/title" mode="titlebar"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="document($xml, /)/document/title" mode="nologo"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> |
V předchozích šablonách jsme explicitně volali šablonu pro
elementy <title>
, <titlebar>
a <head>
. Při zpracování těla dokumentu se již musí
tyto elementy ignorovat. Vytvoříme tedy dvě šablony. První z nich bude
obsahovat atribut mode='title titlebar'
. Šablona provede
běžné zpracování vnořených elementů ve stejném režimu, protože šablony
pro vnořené elementy se také mohou lišit. Druhá šablona, která se bude
volat ve standardním režimu, bude prázdná, takže elementy budou
ignorovány.
<xsl:template match="title|titlebar|head" mode="title titlebar nologo"> <xsl:apply-templates mode="#current"/> </xsl:template> <xsl:template match="title|titlebar|head"/> |
Vytvoření odkazu na kaskádový styl je složitější. Pokud
jsme v atributu css
elementu <document>
uvedli přímo URL, je to jednoduché. Problém nastává, pokud jsme místo
URL uvedli tečku. Pak se musí vyhodnotit jméno a umístění standardního
stylu. Aby stránky nebyly závislé na konkrétním umístění a daly se
testovat lokálně, musí být URL relativní. Musíme tedy doplnit správný
počet zpětných kroků z globální proměnné backdir
a přidat
jméno stylu.
<xsl:template name="doc-css"> <xsl:for-each select="tokenize(@css, '\s+')"> <xsl:variable name="css"> <xsl:choose> <xsl:when test=".='.'"> <xsl:value-of select="$backdir"/> <xsl:text>css/style.css</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:variable> <link rel="stylesheet" href="{$css}" type="text/css"/> </xsl:for-each> <xsl:variable name="favicon" select="concat($backdir, 'images/favicon.ico')"/> <link rel="shortcut icon" type="image/x-icon" href="{$favicon}"/> </xsl:template> |
V několika šablonách budeme potřebovat číslo sekce.
Vytvoříme si tedy odpovídající šablonu, kterou pak opakovaně
použijeme. Element <xsl:number>
nám při použití
atributu level='multiple'
automaticky očísluje sekce
hierarchicky.
<xsl:template name="secnum"> <xsl:number level="multiple" format="1." count="section[not(@role) or @role != $ignore-role]"/> </xsl:template> |
Podobně si vytvoříme opakovatelně použitelnou šablonu,
která vytvoří návěští. Chceme-li se na kapitolu odvolávat sami z jiného místa, musíme jméno návěští uvést v elementu
<section>
v atributu label
. Pokud nám
stačí jen použití v automaticky generovaném obsahu, necháme šablonu,
aby vytvořila jedinečný identifikátor.
<xsl:template name="seclabel"> <xsl:choose> <xsl:when test="@label"> <xsl:value-of select="@label"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="generate-id()"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Obsah elementu zpracujeme pouze v případě, že to není
zakázáno atributem role
(viz Šablony pro manuál).
Nejprve si hodnotu návěští uložíme do proměnné seclabel
,
abychom s ní mohli pohodlněji pracovat. Pak musíme vytvořit element s nadpisem. Úroveň nadpisu získáme z parametru hdr
.
Vzpomeňte si, že šablona pro element <document>
při
volání dalších šablon tomuto parametru nastavila hodnotu 2.
Element <section>
nejvyšší úrovně tedy bude mít
nadpis v elementu <h2>
. Nadpis bude mít jedinečné
návěští a bude automaticky očíslován. Všimněte si, že šablonu pro
element <title>
znovu voláme v režimu
title
. Při volání šablon pro další elementy musíme zvýšit
hodnotu parametru hdr
, aby vnořené sekce používaly
správný element pro nadpis. Kořenový element
<document>
může obsahovat atribut top
určující nejhlubší úroveň nadpisu elementu <section>
,
u něhož máme ještě vytvářet odkaz na začátek stránky. Úroveň nadpisu
je uložena v hodnotě proměnné hdr
. Několik vnořených
sekcí může končit na stejném místě. Pak bychom dostali několik odkazů
na začátek stránky za sebou. Proto podmínkou
count(following-sibling::node()) > 0
ověříme, že
za koncem sekce máme ještě nějaké uzly. Bílé mezery jsou ignorovány,
protože jsme v šabloně použili <xsl:strip-space>
všude
s výjimkou elementu <xsl:text>
.
<xsl:template match="section"> <xsl:param name="hdr"/> <xsl:if test="not(@role) or @role != $ignore-role"> <xsl:variable name="label"> <xsl:call-template name="seclabel"/> </xsl:variable> <xsl:element name="{concat('h', string($hdr))}"> <xsl:attribute name="id" select="$label"/> <xsl:call-template name="secnum"/> <xsl:text> </xsl:text> <xsl:apply-templates select="title" mode="title"/> </xsl:element> <xsl:apply-templates> <xsl:with-param name="hdr" select="$hdr + 1"/> </xsl:apply-templates> <xsl:if test="/document/@top>=$hdr and count(following-sibling::element()[name()!='zw-a'] except following-sibling::section[@role=$ignore-role]/descendant-or-self::element())>0"> <xsl:call-template name="top-link"/> </xsl:if> </xsl:if> </xsl:template> |
Nadpis může obsahovat hypertextový odkaz, který je nutno zachovat.
Zavoláme si tedy šablonu pro <zw-a>
, která pracuje v požadovaném režimu.
<xsl:template match="a" mode="title"> <xsl:call-template name="zw-a"/> </xsl:template> |
Odkaz na začátek stránky si vložíme do jednobuňkové
orámované tabulky zarovnané vpravo, jejíž pozadí bude obarveno
kaskádovým stylem. Nemůžeme obarvit přímo pozadí elementu
<div>
, protože bychom získali barevný pruh přes
celou šířku okna prohlížeče.
<xsl:template name="top-link"> <xsl:variable name="top"> <xsl:text> </xsl:text> <xsl:call-template name="top"/> <xsl:text> </xsl:text> </xsl:variable> <div> <table class="top" align="right" border="1" frame="box" rules="none"> <tr> <td> <a href="#top" title="{$top}"> <xsl:value-of select="$top"/> </a> </td> </tr> </table> </div> </xsl:template> |
Text, který bude zobrazen v odkazu na začátek stránky, si vytvoříme pro přehlednost v oddělené šabloně.
<xsl:template name="top"> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Začátek stránky</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Top of the page</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Top of the page</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template> |
Popis šablon pro obsah zahájíme opět pomocnou textovou
šablonou. Všimněte si, že šablona má atribut match
, aby
mohla sloužit ke zpracování elementu <toc-heading>
, i atribut name
, aby mohla být volána jménem z jiné šablony.
<xsl:template name="toc-heading" match="toc-heading"> <p> <b> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Obsah</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Contents</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='hi'"> <xsl:text>विषय-सूची</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Contents</xsl:text> </xsl:otherwise> </xsl:choose> </b> </p> </xsl:template> |
Element <toc>
slouží k zobrazení obsahu,
a to jak na začátku stránky, tak obsahu konkrétní sekce. Pokud však
daná sekce žádné vnořené sekce nemá, měl by se element ignorovat.
Musíme tedy nejprve zjistit, zda element, v němž je
<toc>
uveden, nějaké sekce obsahuje. To by se dalo
snadno ověřit podmínkou count(../section) > 0
. My
však budeme potřebovat i maximální hloubku vnoření. K jejímu určení
potřebujeme XPath 2.0. Cyklem for
vytvoříme
sekvenci všech elementů <section>
, které již žádnou
sekci neobsahují, a vypočteme počet předků těchto elementů. Do
sekvence jsme přidali též kořenový uzel, aby sekvence nebyla prázdná,
kdyby daná sekce již žádné vnořené sekce neobsahovala. Současně jsme
odebrali sekce, jež mají být ignorovány (viz Šablony pro manuál).
Najdeme maximum počtu předků a od něj odečteme počet předků aktuální
sekce. Je-li hloubka vnoření kladná, bude obsah zobrazen. Nejprve
zpracujeme obsah elementu <toc>
. Je li obsah elementu
prázdný a současně má atribut heading
hodnotu
yes
, zavoláme šablonu toc-heading
. Nakonec
aplikujeme šablony na rodičovský element <section>
.
Všimněte si, že šablony budou zpracovány v režimu toc
a předáváme jako parametr pojmenovaný span
hodnotu proměnné
depth
.
<xsl:template match="toc"> <xsl:variable name="depth"> <xsl:value-of select="max(for $s in ((..//section[not(section)]|/) except (//element()[role=$ignore-role]/descendant-or-self::element())) return count($s/ancestor::element())) - count(../ancestor::element())"/> </xsl:variable> <xsl:if test="$depth > 0"> <xsl:choose> <xsl:when test="count(*) > 0"> <xsl:apply-templates/> </xsl:when> <xsl:when test="@heading='yes'"> <xsl:call-template name="toc-heading"/> </xsl:when> </xsl:choose> <table border="0" frame="none" rules="none" class="toc"> <xsl:apply-templates select="../section" mode="toc"> <xsl:with-param name="span" select="$depth"/> </xsl:apply-templates> </table> </xsl:if> </xsl:template> |
Stará metoda používala XPath 1.0 a rozšíření
procesoru Saxon. Začínali jsme výrazem ../section
a v
cyklu <saxon:while>
jsme do něj postupně přidávali
/section
a přičítali jedničku do proměnné
depth
. Tehdy ještě nebyl automaticky generován offline
manuál, takže element <section>
neměl atribut
role
.
Zastaralý kód |
---|
<xsl:variable name="depth" select="0" saxon:assignable="yes"/> <xsl:variable name="path" saxon:assignable="yes"> <xsl:text>../section</xsl:text> </xsl:variable> <saxon:while test="count(saxon:evaluate($path)) > 0"> <saxon:assign name="depth" select="$depth + 1"/> <saxon:assign name="path"> <xsl:value-of select="concat($path, '/section')"/> </saxon:assign> </saxon:while> |
Nyní jsme se dostali ke kódu, který vkládá záznamy do
obsahu. Máme-li vnořené sekce, chtěli bychom, aby v obsahu byly
odpovídajícím způsobem odsazeny. Toho lze dosáhnout nějakým výčtovým
elementem v HTML. Bohužel, <ol>
nám první úroveň
očísluje, ke druhé úrovni přidá písmena, a už vůbec nezachová
hierarchické číslování. Sice by to šlo vyřešit v kaskádovém stylu, ale
jak již bylo řečeno, kaskádové styly nejsou všude implementovány
správně, v některých prohlížečích nejsou implementovány vůbec. Obsah
musíme tedy vytvořit tak, aby byl viditelný i v prohlížeči, který
kaskádovému stylu nerozumí. Proto předchozí šablona založila pro obsah
tabulku. Abychom mohli odsazovat nadpisy vnořených sekcí, musíme na
začátku každého řádku tabulky uvést prázdnou buňku přes odpovídající
počet sloupců. V následujícím sloupci je číslo zarovnané vpravo a na
závěr vložíme přes odpovídající počet sloupců název sekce. Název
získáme z elementu <title>
, ale jeho šablonu musíme
volat v režimu toc
. Číslo i návěští zjistíme pomocí
šablon, které jsme používali již dříve.
Parametru span
byla při prvním volání předána
hodnota maximálního vnoření, parametr indent
měl
defaultní nulovou hodnotu. Po zápisu řádku zpracujeme vnořený element
<section>
, přičemž indent
o jedničku
zvětšíme a span
zmenšíme. Rekurze tedy dospěje do
okamžiku, kdy budeme zpracovávat vnořenou sekci s nulovou hodnotou
parametru span
. Tabulka se nám pak rozjede. Jenže
naštěstí k tomu nemůže dojít. Předem jsme si vypočetli maximální
hloubku vnoření a taková sekce tedy v dokumentu není. Nemusíme tedy v šabloně používat podmínku, protože
<xsl:apply-templates>
žádný element pro nulovou hodnotu
parametru span
nenajde.
Podobně jako v kapitole "Kapitoly a podkapitoly" zpracujeme
pouze elementy, kde to není zakázáno atributem role
.
<xsl:template match="section" mode="toc"> <xsl:param name="indent" select="0"/> <xsl:param name="span" select="-1"/> <xsl:if test="not(@role) or @role != $ignore-role"> <xsl:variable name="label"> <xsl:call-template name="seclabel"/> </xsl:variable> <tr> <xsl:if test="$indent > 0"> <td> <xsl:if test="$indent > 1"> <xsl:attribute name="colspan"> <xsl:value-of select="$indent"/> </xsl:attribute> </xsl:if> <xsl:text> </xsl:text> </td> </xsl:if> <td align="right"> <a href="{concat('#', $label)}"> <xsl:call-template name="secnum"/> </a> </td> <td> <xsl:if test="$span > 1"> <xsl:attribute name="colspan"> <xsl:value-of select="$span"/> </xsl:attribute> </xsl:if> <a href="{concat('#', $label)}"> <xsl:apply-templates select="title" mode="toc"/> </a> </td> </tr> <xsl:apply-templates select="section" mode="toc"> <xsl:with-param name="indent" select="$indent + 1"/> <xsl:with-param name="span" select="$span - 1"/> </xsl:apply-templates> </xsl:if> </xsl:template> |
Hypertextové odkazy budeme v režimu toc
ignorovat, bude zachován pouze jejich obsah.
<xsl:template match="a" mode="toc"> <xsl:call-template name="zw-a-content"/> </xsl:template> |
Ze všech stránek by mělo být jasné, komu patří. Proto budeme chtít v pravém horním rohu logo s odkazem na hlavní stránku. Vytvoříme si tedy užitečnou šablonu.
<xsl:template match="zw-logo" mode="#default title"> <a title="{$mainpage}" href="{$home}"> <img src="{concat($backdir, 'images/ibslogo1.gif')}" width="175" height="45" alt="Ice Bear Soft" align="right" border="0"/> </a> </xsl:template> |
Element <zw-logo>
se bude používat na
začátku elementu <title>
. V režimu
titlebar
jej tedy budeme ignorovat.
<xsl:template match="zw-logo" mode="titlebar nologo"/> |
Často se odkazujeme na vlastní dokumenty a chtěli bychom,
aby se atribut title
a text doplnil z názvu příslušného
dokumentu. Element <a>
tedy zpracujeme stejnou
šablonou, kterou jsme použili pro element
<zw-a>
v režimu footer
. Stejně
zpracujeme pomocný element <zw-label>
.
<xsl:template match="a|zw-label"> <xsl:call-template name="zw-a"/> </xsl:template> |
Některé obrázky chceme zobrazit v samostatném okně pomocí externí JavaScriptové funkce. Kód pro její volání vygenerujeme v PHP, takže výstupem šablony bude instrukce pro zpracování.
<xsl:template match="zw-jsimg"> <xsl:processing-instruction name="php"><xsl:text>jsimg('</xsl:text> <xsl:value-of select="@src"/><xsl:text>', '</xsl:text> <xsl:value-of select="@align"/><xsl:text>');</xsl:text> ?</xsl:processing-instruction> </xsl:template> |
Funkce jsimg
spoléhá na images.js a obsahuje:
# JavaScript images function jsimg($src, $align) { if ($align) $align = "align=\"$align\""; $sz = GetImageSize($src); $thsrc = ''; if (preg_match('/^(.+)(\.[^.]+)$/', $src, $parts)) { $thsrc = $parts[1] . '_small' . $parts[2]; $thsz = GetImageSize($thsrc); ?> <script language="JavaScript"> <!-- <?php printf("img('%s', %d, %d, '%s', %d, %d, '%s')", $thsrc, $thsz[0], $thsz[1], $src, $sz[0], $sz[1], $align); ?> // --> </script> <noscript> <a href="<?php echo $src; ?>" target="image"> <img src="<?php echo $thsrc; ?>" hspace=5 vspace=1 width=<?php echo $thsz[0]; ?> height=<?php printf('%d %s', $thsz[1], $align); ?>> </a></noscript> <?php }} |
Někdy potřebujeme do zdrojového souboru napsat text, který
se má na výstupu objevit jako komentář. Vytvoříme si proto element
<zw-comment>
. Může se hodit například při vytváření
instrukcí pro kód vkládaný na straně serveru (server side includes).
<xsl:template match="zw-comment"> <xsl:comment><xsl:apply-templates/></xsl:comment> </xsl:template> |
Některé elementy nechceme z bezpečnostních důvodů
zobrazit. Uzavřeme je proto do elementu <zw-hide>
a přidáme náhradní text do atributu alt
.
<xsl:template match="zw-hide"> <span class="hide"> <xsl:value-of select="@alt"/> </span> </xsl:template> |
Někdy potřebujeme uložit do části dokumentu speciální
informaci, přičemž nechceme využít jen komentáře. Hodilo by se,
kdybychom části dokumentu mohli označit a jednoduchým způsobem volit,
zda se mají při generování stránek zpracovat. Zavedeme si proto
element <zw-info>
, který může mít libovolné atributy
s libovolným obsahem. Má-li atribut include
hodnotu
yes
, bude obsah tohoto elementu zpracován. Má-li
atribut include
jinou hodnotu nebo zcela chybí, bude
obsah elementu ignorován.
<xsl:template match="zw-info"> <xsl:if test="@include = "yes""> <xsl:apply-templates/> </xsl:if> </xsl:template> |
Instrukce pro zpracování PHP budou pouze zkopírovány, avšak s připojeným otazníkem na konci.
<xsl:template match="processing-instruction("php")"> <xsl:processing-instruction name="php"><xsl:value-of select="."/><xsl:text>?</xsl:text> </xsl:processing-instruction> </xsl:template> |
Ostatní instrukce pro zpracování budou ignorovány.
<xsl:template match="processing-instruction()" mode="#all"/> |
Všechny ostatní elementy, které nemají explicitní šablony,
pouze zkopírujeme včetně atributů. Nechceme zde použít
<xsl:copy>
, protože by se nám do výstupního elementu
dostaly deklarace všech jmenných prostorů. Stejnou činnost provádíme
ve standardním režimu i v režimu title
.
<xsl:template match="*" mode="#default title nologo"> <xsl:element name="{name()}"> <xsl:apply-templates select="@*"/> <xsl:apply-templates mode="#current"/> </xsl:element> </xsl:template> |
Kopii atributů lze vytvořit snadno pomocí
<xsl:copy-of>
.
<xsl:template match="@*"> <xsl:copy-of select="."/> </xsl:template> |
V některých šablonách budeme vkládat celé dokumenty, ale
nechceme generovat hlavičku. Použijeme proto režim
include
.
<xsl:template match="/" mode="include"> <xsl:apply-templates/> </xsl:template> |
Na závěr si uvedeme šablonu pro odkaz na stránky protestu proti SW patentům.
<xsl:template match="zw-nopatents" mode="#default title"> <xsl:variable name="title"> <xsl:choose> <xsl:when test="$DocumentLanguage = "cs""> <xsl:text>Žádné softwarové patenty!</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>No software patents!</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <a href="http://noepatents.eu.org" title="{$title}"> <img width="480" height="60" src="{concat($backdir, 'images/swpatbanner.en.png')}"> <xsl:copy-of select="@*"/> </img> </a> </xsl:template> |
V dokumentu se často odvoláváme na nějaké elementy. Bylo
by nepohodlné, kdybychom museli do elementu <code>
přepisovat úhlové závorky pomocí entit <
a >
a nakonec použít ukončovací tag </code>
. Vytvoříme si tedy pomocnou šablonu pro element
<zw-elem>
. Element může mít atribut q
,
jehož jediná smysluplná hodnota je lomítko. Písmeno q
jsme vybrali pouze proto, že je na kraji klávesnice.
<xsl:template match="zw-elem" mode="#default title toc nologo"> <xsl:call-template name="zw-elem"> <xsl:with-param name="prefix"> <xsl:value-of select="@q"/> </xsl:with-param> </xsl:call-template> </xsl:template> |
Často se odvoláváme i na elementy s prefixem
xsl:
. Abychom jej nemuseli explicitně uvádět, vytvoříme
si element <zw-xelem>
, který jej doplní sám.
<xsl:template match="zw-xelem" mode="#default title toc nologo"> <xsl:call-template name="zw-elem"> <xsl:with-param name="prefix"> <xsl:value-of select="@q"/> <xsl:text>xsl:</xsl:text> </xsl:with-param> </xsl:call-template> </xsl:template> |
Celou práci provede následující šablona. Od předchozích
šablon dostane parametr prefix
, který může obsahovat
lomítko a/nebo prefix xsl:
.
<xsl:template name="zw-elem"> <xsl:param name="prefix"/> <code> <xsl:text><</xsl:text> <xsl:value-of select="$prefix"/> <xsl:apply-templates/> <xsl:text>></xsl:text> </code> </xsl:template> |
Styl pro bootstraping vytvářel transformační styl pro konverzi do HTML z obsahu
elementů <zw-pre>
. My nyní obsah těchto elementů
zobrazíme jako příklady kódu. Pro větší přehlednost jim kaskádovým
stylem dáme žluté pozadí. Za jistých okolností však mohou být řádky
dlouhé a přesáhnou šířku okna prohlížeče. Vlastnost
background-color
pak nefunguje správně. Zdá se, že
pozadí tabulky se obarví správně vždy. Obsah elementu
<zw-pre>
tedy zobrazíme v jednosloupcové jednořádkové
tabulce. Prohlížeč, který nerozumí kaskádovým stylům, jej alespoň
zarámuje. Pouze v případě, že element <zw-pre>
má
atribut lang
, přidáme k tabulce nadpis.
V dokumentu nyní nemůžeme použít element
<pre>
, protože by se nám rozpadlo formátování tohoto
příkladu. Místo něj si tedy zavedeme synonymum
<verbatim>
. Tento nový element se liší od
<zw-pre>
zejména tím, že jeho obsah se nedostane do
vygenerovaného transformačního stylu. Konec řádku, vkládaný na
několika místech, je estetická záležitost. Důležité je pouze to, že
vnořené elementy zpracováváme v režimu verbatim
.
<xsl:template match="zw-pre|verbatim" name="verbatim"> <xsl:text> </xsl:text> <table border="2" frame="box" rules="rows" cellpadding="5" class="pre"> <xsl:if test="@mode"> <tr> <th> <xsl:choose> <xsl:when test="@mode="old""> <xsl:choose> <xsl:when test="$DocumentLanguage='cs'"> <xsl:text>Zastaralý kód</xsl:text> </xsl:when> <xsl:when test="$DocumentLanguage='en'"> <xsl:text>Obsolete code</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Obsolete code</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:text>mode = </xsl:text> <xsl:value-of select="@mode"/> </xsl:otherwise> </xsl:choose> </th> </tr> </xsl:if> <tr> <td> <pre> <xsl:if test="name() != 'verbatim'"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates mode="verbatim"/> </pre> </td> </tr> </table> <xsl:text> </xsl:text> </xsl:template> |
Nyní zpracujeme všechny elementy. Protože v HTML musí být
převedeny úhlové závorky na znakové entity, nebude procesor XSLT
výstup považovat za elementy a odsazení tedy musíme vložit sami.
Hodnotu odsazení máme v parametru indent
, jehož hodnota
je zpočátku prázdná. Za entitou označující otvírací závorku zapíšeme
jméno elementu a zavoláme šablonu pro atributy, pochopitelně v režimu
verbatim
. Pokud je element prázdný, ukončíme jej
lomítkem a pravou závorkou, v opačném případě zavoláme šablonu pro
vložené elementy, přičemž zvětšíme odsazení. Pak musíme ještě zapsat
ukončovací tag.
<xsl:template match="*" mode="verbatim"> <xsl:param name="indent"/> <xsl:param name="amount"> <xsl:text> </xsl:text> </xsl:param> <xsl:param name="endline"> <xsl:text> </xsl:text> </xsl:param> <xsl:value-of select="$indent"/> <xsl:text><</xsl:text> <xsl:value-of select="name()"/> <xsl:apply-templates select="@*" mode="verbatim"/> <xsl:choose> <xsl:when test="count(*)=0 and string-length(.)=0"> <xsl:text>/></xsl:text> <xsl:value-of select="$endline"/> </xsl:when> <xsl:otherwise> <xsl:text>> </xsl:text> <xsl:apply-templates mode="verbatim"> <xsl:with-param name="indent"> <xsl:value-of select="concat($indent, $amount)"/> </xsl:with-param> </xsl:apply-templates> <xsl:value-of select="$indent"/> <xsl:text></</xsl:text> <xsl:value-of select="name()"/> <xsl:text>></xsl:text> <xsl:value-of select="$endline"/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Obsah elementu <pre>
zobrazíme
analogicky, ale ponecháme formátování, jaké bylo v elementu použito.
Element je totiž uveden v
<xsl:preserve-spaces>
.
<xsl:template match="pre"> <table border="2" frame="box" rules="rows" cellpadding="5" class="pre"> <tr> <td> <pre> <xsl:apply-templates/> </pre> </td> </tr> </table> </xsl:template> |
Šablony pro zpracování elementů <xsl:text>
,
<zw-text>
,
<xsl:processing-instruction>
a <xsl:comment>
jsou mírně odlišné. Chceme totiž element
z celým obsahem ponechat na jednom řádku.
<xsl:template match="xsl:text|zw-text|xsl:comment|xsl:processing-instruction" mode="verbatim"> <xsl:param name="indent"/> <xsl:value-of select="$indent"/> <xsl:text><</xsl:text> <xsl:value-of select="name()"/> <xsl:apply-templates select="@*" mode="verbatim"/> <xsl:text>></xsl:text> <xsl:choose> <xsl:when test="count(*)=0"> <xsl:value-of select="."/> </xsl:when> <xsl:otherwise> <xsl:apply-templates mode="verbatim"> <xsl:with-param name="endline"/> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> <xsl:text></</xsl:text> <xsl:value-of select="name()"/> <xsl:text>> </xsl:text> </xsl:template> |
Zpracování atributů je snadné. Zapíšeme jméno atributu, rovnítko a hodnotu v uvozovkách.
<xsl:template match="@*" mode="verbatim"> <xsl:text> </xsl:text> <xsl:value-of select="name()"/> <xsl:text>="</xsl:text> <xsl:value-of select="normalize-space(.)"/> <xsl:text>"</xsl:text> </xsl:template> |
V textových uzlech požadujeme normalizaci mezer.
<xsl:template match="text()" mode="verbatim"> <xsl:value-of select="normalize-space(.)"/> </xsl:template> |
Relativní cesta k transformačnímu souboru pro bootstraping
je definována v dokumentu jako externí entita pojmenovaná
bootstrap
. V kapitole Bootstrapping je styl načten uvedením
&bootstrap;
. Jeho kořenovým elementem je
<xsl:stylesheet>
. Následující šablona tedy zpracuje
obsah souboru stejnou šablonou, kterou zde používáme na elementy
<zw-pre>
. Všimněte si, že kořenový element se ve výpisu
neobjeví. Je to jediný element, který byl v dokumentaci připsán ručně.
<xsl:template match="xsl:stylesheet"> <xsl:call-template name="verbatim"/> </xsl:template> |
V textových uzlech v češtině chceme doplnit nezlomitelné mezery za
jednopísmenné předložky a spojky. Bohužel ve výpisu není vidět ani nezlomitelná mezera za
$1
ani to, že poslední hranatá závorka v druhém parametru funkce
replace
obsahule
[ 
]
.
<xsl:template match="text()"> <xsl:variable name="lang" as="node()*" select="(ancestor-or-self::*/@xml:lang | ancestor-or-self::*/@lang)[last()]"/> <xsl:choose> <xsl:when test="$lang = 'cs'"> <xsl:value-of select="replace(., '([ (,][AaIiKkOoSsUuVvZz])[ ]+', '$1 ')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:template> |
Nezlomitelné mezery nesmí být vkládány v elementech pro zápis kódu.
<xsl:template match="zw-pre/text()|pre/text()|code/text()|verbatim/text()"> <xsl:value-of select="."/> </xsl:template> |
7.1. | Modifikované šablony pro manuál |
7.2. | Logo s hypertextovým odkazem pro manuál |
7.3. | Zpracování kořenového elementu |
7.4. | Postprocessing |
7.5. | Vložení odkazu na kaskádový styl |
7.6. | Zápatí stránky |
Transformační styl pro manuál importuje styl pro transformaci do HTML a pouze předefinuje několik šablon a proměnných.
mode = offline |
---|
<xsl:import href="xml2html.xsl"/> <xsl:variable name="ignore-role"> online</xsl:variable> |
Výklad těchto šablon byl již uveden v kapitole "Šablony pro manuál". Zde je definována modifikovaná verze s prohozenými rolemi.
mode = offline |
---|
<xsl:template match="zw-offline"> <xsl:apply-templates/> </xsl:template> <xsl:template match="zw-online"/> |
Hypertextový odkaz spojený s logem musí odkazovat na stránku na internetu. Logo musí být přidáno k manuálu, aby jej bylo možno prohlížet skutečně offline. Jeho umístění proto určíme parametrem.
mode = offline |
---|
<xsl:variable name="ibslogo"> ibslogo1.gif</xsl:variable> <xsl:variable name="home"> http://icebearsoft.euweb.cz/</xsl:variable> <xsl:template match="zw-logo" mode="#default title"> <a title="{$mainpage}" href="{$home}"> <img src="{$ibslogo}" width="175" height="45" alt="Ice Bear Soft" align="right" border="0"/> </a> </xsl:template> |
Manuál je vždy čisté HTML bez kódu v PHP. Vytvoříme proto
element <html>
a v něm zpracujeme element
<document>
. Hypertextové odkazy mohou vznikat
nejrůznějšími mechanismy. Abychom z relativních odkazů, směřujících na
jiné stránky, udělali absolutní, uložíme si výsledek transformace do
proměnné a následně jej budeme transformovat znovu. Předpokládáme, že
obrázky i kaskádové styly jsou přiloženy, takže jejich odkazy
upravovat nemusíme.
mode = offline |
---|
<xsl:template match="/"> <xsl:variable name="offline-document" as="element()"> <html> <xsl:apply-templates select="document"/> </html> </xsl:variable> <xsl:apply-templates select="$offline-document" mode="offline-postprocessing"/> </xsl:template> |
Jediné, co musíme dodatečně zpracovat, je atribut
href
elementu <a>
. Nebudeme zasahovat
do odkazů uvnitř stránky ani do odkazů absolutních.
mode = offline |
---|
<xsl:template match="a" mode="offline-postprocessing"> <a> <xsl:copy-of select="@*"/> <xsl:if test="name(..) != 'span' and name(..) != 'div' and (not(../@id) or ../@id != 'langlinks') and not(contains(@href, '://')) and not(starts-with(@href, '#'))"> <xsl:attribute name="href"> <xsl:call-template name="postprocess-href"> <xsl:with-param name="href"> <xsl:value-of select="replace(concat($referer, @href), '/\./', '/')"/> </xsl:with-param> </xsl:call-template> </xsl:attribute> </xsl:if> <xsl:apply-templates mode="#current"/> </a> </xsl:template> |
Proměnná referer
obsahuje základní URL, tj.
http://icebearsoft.euweb.cz/
a část jména souboru za
/xml/
až k poslednímu lomítku.
mode = offline |
---|
<xsl:variable name="referer"> <xsl:text>http://icebearsoft.euweb.cz/</xsl:text> <xsl:analyze-string select="substring-after(saxon:systemId(), '/xml/')" regex="^(.*?)[^/]+$"> <xsl:matching-substring> <xsl:value-of select="regex-group(1)"/> </xsl:matching-substring> </xsl:analyze-string> </xsl:variable> |
Nyní se rekurzivní šablonou zbavíme odkazů do rodičovského adresáře.
mode = offline |
---|
<xsl:template name="postprocess-href"> <xsl:param name="href" as="xs:string" required="yes"/> <xsl:analyze-string select="$href" regex="$(.*?)/[^/]+/\.\.(/.*)$"> <xsl:matching-substring> <xsl:call-template name="postprocess-href"> <xsl:with-param name="href"> <xsl:value-of select="concat(regex-group(1), regex-group(2))"/> </xsl:with-param> </xsl:call-template> </xsl:matching-substring> <xsl:non-matching-substring> <xsl:value-of select="."/> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:template> |
Ostatní elementy budou zkopírovány včetně svých atributů.
mode = offline |
---|
<xsl:template match="*" mode="offline-postprocessing"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="#current"/> </xsl:copy> </xsl:template> |
Podobně jako logo i kaskádový styl musí být přibalen. Jméno a umístění definujeme v parametru transformačního stylu.
mode = offline |
---|
<xsl:variable name="css"> style.css</xsl:variable> <xsl:template name="doc-css"> <link rel="stylesheet" href="{$css}" type="text/css"/> </xsl:template> |
Zápatí bude obsahovat pouze copyright a odkazy na jazykové varianty, nic jiného se zde vyskytovat nebude. Soubory s jinými jazykovými verzemi budou určeny parametrem, kde hlavní jméno souboru (bez pomlčky a bez přípony) bude následováno dvojtečkou, za níž budou uvedeny jazykové kódy oddělené čárkami.
mode = offline |
---|
<xsl:param name="langlinks" as="xs:string" required="yes"/> <xsl:template name="footer"> <br/> <br/> <br/> <hr/> <div id="copyright"> <xsl:text>© Z. Wagner - Ice Bear Soft, </xsl:text> <xsl:value-of select="zw:last-modified()" use-when="function-available("zw:last-modified")"/> </div> <xsl:text> </xsl:text> <xsl:if test="contains($langlinks, ':')"> <div id="langlinks"> <xsl:text> </xsl:text> <xsl:for-each select="tokenize(substring-after($langlinks, ':'), ',')"> <a href="{concat(substring-before($langlinks, ':'), '-', ., '.html')}"> <xsl:value-of select="upper-case(.)"/> </a> <xsl:text> </xsl:text> </xsl:for-each> </div> </xsl:if> </xsl:template> |
Můžete si stáhnout vygenerované transformační styly. Budete též potřebovat modul s rozšiřující funkcí procesoru Saxon.
Celý postup vytváření stránek vyžaduje několik akcí, které je nutno provést ve správném pořadí. Přitom některé z nich provést nemusíme, pokud se zdrojové soubory nezměnily. Pro automatizaci celého procesu je použit Ant. Pro uživatele OS/2 mám částečně smutnou zprávu. Spouštěcí skripty byly vytvořeny pro verzi 1.6alpha. Ve verzi 1.6.1 však bylo leccos změněno, takže tyto skripty nefungují. Funkční skripty najdete ve verzi 1.6.2. Informace o změnách si můžete přečíst v Bugzille v chybě číslo 28226. Verze 1.5.x mají chyby specifické pro OS/2, proto je raději nepoužívejte.
Konkrétní antfile, použitý v tomto projektu, bude dokumentován (snad) později. Prozatím si uvedeme jen základní pravidla:
bootstrap.xsl
nebo
webmake.xml
, je nutno vygenerovat znovu všechny
transformační styly xml2html*.xsl
.
xml2html*.xsl
, je nutno vygenerovat znovu všechny HTML
(PHP) soubory.
WWW stránky vytvářím doma na svém počítači s OS/2. Po otestování je třeba nové a modifikované stránky pomocí FTP odeslat na server. Tato činnost je automatizována pomocí PHP skriptu. PHP však musí běžet jako samostatný program. Skript očekává čtyři parametry:
PHP 4.0.6 pro OS/2 měl chybu ve funkci
ftp_put
. Chyba byla odstraněna ve verzi 4.1.0.
PHP 4.2.3 (php.exe
) nezná proměnnou
$argc
, takže nelze rozebírat parametry příkazového řádku,
verze 4.3.4 tuto chybu nemá. Bohužel ve verzi 4.3.4 nefunguje
ImageJPEG
. Pokud potřebujete mít PHP modul v Apachi,
generovat dynamicky obrázky a jiné binární soubory a současně používat
php.exe
, doporučuji modul 4.2.3 pro Apache a 4.3.4 pro
použití z příkazového řádku. Verze 4.3.4 však vyžaduje jiný
php.ini
. Musíte jej specifikovat při volání
php.exe
pomocí parametru -c
.
Zde najdete:
PHP skript používá funkce ftp_size
a ftp_mdtm
, které nemusí na některých FTP serverech
fungovat.