Parę dni temu stanąłem przed palącym problemem: chodziło mianowicie o udokumentowanie mojego jednego projektu. Początkowo wszystko robiłem po prostu we FrontPage'u pracowicie wklepując tekst i ręcznie nadając odpowiedni wygląd. Uniemożliwiało mi to jednak prace nad dokumentacją w systemie Linux, gdzie strasznie trudno o dobry edytor WYSIWYG. Ręczne wpisywanie kodu HTML nie wchodziło w grę ze względu na toporność. Mimo to znalazłem wreszcie rozwiązanie.
Przypomniałem sobie o XML'u - języku rozszerzalnych znaczników. Postanowiłem wykorzystać właśnie ten meta-język do stworzenia aplikacji tworzenia i przeglądania dokumentacji. W tym artykule chciałbym właśnie przedstawić wyniki czterodniowych zmagań z tym zadaniem.
Wiele znających mnie osób myśli teraz pewnie, że najpierw omówię po prostu, w jaki sposób zapiszemy w pliku XML strukturę manuala, a potem uruchomię Apacha, PHP i napiszę ręcznie odpowiedni parser :). Tymczasem nie - do pracy wystarczy nam tym razem zwykła przeglądarka potrafiąca przetwarzać dokumenty XML (np. Internet Explorer, wszystkie oparte na mechaniźmie Gecko), oraz notatnik. Jak to wykonamy? Jak wielu osobom wiadomo, języki/technologie oparte na XML'u zwą się aplikacjami XML'a. Jedną z nich jest XSL (eXtensible Stylesheet Language), który służy do określania, w jaki sposób przeglądarka ma zaprezentować dany dokument użytkownikowi. I to jest nasz klucz do sukcesu. Całe zadanie będzie sprowadzało się do utworzenia Definicji Typu Dokumentu (DTD), czyli określenia, jakie znaczniki mają co i gdzie pobierać :), a następnie utworzenia arkusza XSL, który każdą stronę manuala wyświetli tak, jakby była ona zrobiona w zwykłym HTML'u. A więc do dzieła!
Zacznijmy od określenia naszych celów. Mamy stworzć aplikację XML do przetwarzania dokumentacji. Nasza będzie składała się z dwóch części:
-
man-menu - system przetwarzania menu naszej dokumentacji
-
man-common - system przetwarzania stron z właściwą treścią.
Dzięki takiej budowie będziemy posiadali odpowiednio wyspecjalizowane systemy, które w pełni będą odpowiadały naszym celom. System powinien mieć możliwość zdefiniowania autorów manuala, jego menu, a dla każdej podstrony: listy odnośników "Zobacz także", jej autorów. Powinien także dać programiście możliwość odpowiedniego formatowania tekstu każdej strony.
Wpierw utworzymy sobie strukturę katalogów. Załóż katalog główny: "fillar-man" - w nim będą znajdowały się wszystkie pliki. W nim utwórz trzy kolejne: "dtd", "style" i "xsl". My wypełnimy pierwszy i ostatni - środkowy pozostał dla ciebie. Po co on, już tłumaczę. Dzięki użyciu arkuszy XSL zmiana layoutu manuala będzie tylko kwestią napisania nowego arkusza. Arkusz taki generuje kod HTML, w który ubrane są dane z dokumentu. Jednak jako że wielu nie uznaje dokumentów HTML bez CSS'a, dajemy im możliwość ich wykorzystania :). Będą oni mogli sobie utworzyć plik CSS właśnie w katalogu "css", oraz ustawić w XSL'u, by ten wysyłał znacznik <link rel>.
Każdy manual powinien mieć jakieś logo, czy też dość duży tekst informujący o tym, co właściwie użytkownik przegląda. Jako że będziemy mieli dwa arkusze XSL niezbędne do działania, takie logo warto umieścić w trzecim - ma to jedną ważną zaletę: zmiana loga w jednym pliku od razu spowoduje jego zmianę na wszystkich podstronach, oraz w menu. Dobra, kończę wreszcie to gadanie, oto źródła pliku "man-top.xsl" (katalog "xsl"):
<?xml version="1.0" encoding="iso-8859-2"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="manual-header">
<table border="0" width="100%" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#EEEEEE"><h1>Przykładowy manual</h1></td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet> |
Jak widać, XSL jest bardzo podobny do XML'a. I to prawda. XSL jest aplikacją zbudowaną na bazie XML'a, tak jak taką aplikacją jest HTML. XSL organizuje sobie cały dokument w
szablony, których wyróżniamy dwa rodzaje. Każdy szablon zawarty jest pomiędzy znacznikami <xsl:template> i </xsl:template> i musi posiadać jeden z dwóch atrybutów:
name, albo
match. Różnica pomiędzy nimi jest zasadnicza:
-
name określa, że podany szablon nie ma nic wspólnego z żadnym ze znaczników przetwarzanego dokumentu.
-
match informuje, że podany szablon odpowiada jakiemuś znacznikowi w przetwarzanym dokumencie.
Użyłem tego pierwszego, gdyż po prostu nie mam zamiaru definiować znacznika "manual-header" w DTD :). Szablon ten jest potrzebny tylko arkuszom XSL, zwykły projektant dokumentacji nawet nie powinien wiedzieć o jego istnieniu.
Zanim przystąpimy do prac nad dalszymi arkuszami, przydałyby się nam pliki DTD informujące (jak już wspominałem) gdzie jaki znacznik ma leżeć i co ma przechowywać. W pierwszej fazie zajmiemy się menu. Oto jego DTD ("man-menu.dtd", katalog "dtd"):
<!ELEMENT introduction (#PCDATA)>
<!ELEMENT authors (author*)>
<!ELEMENT author (#PCDATA)>
<!ATTLIST author email CDATA #IMPLIED>
<!ELEMENT section (link*)>
<!ATTLIST section name CDATA #REQUIRED>
<!ELEMENT item (#PCDATA)>
<!ATTLIST item file CDATA #REQUIRED> |
Składnia jest bardzo prosta do zrozumienia. Tag <!ELEMENT> informuje, jakie podznaczniki ma zawierać element o podanej nazwie. Przyjrzyjmy się deklaracji taga "introduction": <!ELEMENT introduction (#PCDATA)>. Po ELEMENT podajemy nazwę znacznika, który chcemy określać. Następnie w nawiasie wpisujemy, co ma przechowywać. Ciąg #PCDATA oznacza, że parser powinien spodziewać się tam zwykłego tekstu, bez żadnych podznaczników. Z kolei <!ELEMENT authors (author*)> daje do zrozumienia, że wymaga, by znajdowały się w nim tagi "author". Znaczek * przekazuje informację, że tagów "author" może być dowolna ilość, w tym 0. Do dyspozycji mamy też inne:
+ - konieczność wystąpienia danego elementu
? - element może wystąpić albo raz, albo w ogóle.
Możemy również określić więcej elementów oddzielając je przecinkiem:
<!ELEMENT tag (tag1*, tag2, tag3, tag4+)>
albo | (logiczne LUB):
<!ELEMENT tag (tag1 | tag2* | tag3)> <!-- Może wystąpić ALBO tag1, ALBO tag2, ALBO tag3 -->
Zamiast nawiasu możemy też wstawić ANY (dowolna zawartość), lub EMPTY (znacznik nie ma żadnej zawartości, ani atrybutów)
To nie wszystko. Niżej znajduje się tag ATTLIST, który określa, jakie atrybuty/parametry powinny być podane do danego taga. Oto jego składnia: <!ATTLIST tag atrybut typ występowanie>. Typ to CDATA (tekst), lub ID (numer). "Występowanie" określa, czy trzeba koniecznie dany atrybut podawać, czy nie itp. Mamy tu do wyboru kilka opcji: #REQUIRED - wymagany; #IMPLIED - niekonieczny. Jest także #FIXED, jednak nie mogłem znaleźć informacji o jego zastosowaniu.
Dla ATTLIST możemy zdefiniować także wartości domyślne:
<!ATTLIST tag atrybut (wartosc1 | wartosc2 | wartosc 3) #REQUIRED> |
Po takim wstępie do DTD możemy zająć się przygotowaniem arkusza XSL:
<?xml version="1.0" encoding="iso-8859-2"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="man-top.xsl"/>
<xsl:template match="/">
<html >
<head>
<title>
<xsl:value-of select="manual/@title"/> - dokumentacja
</title>
</head>
<body>
<xsl:call-template name="manual-header"/>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td><b>Autorzy:</b></td>
</tr>
<xsl:for-each select="manual/authors/author">
<xsl:choose>
<xsl:when test="@email">
<tr>
<td>
<xsl:element name="a">
<xsl:attribute name="href">
mailto:<xsl:value-of select="@email"/>
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</td>
</tr>
</xsl:when>
<xsl:otherwise>
<tr>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</table>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td><b>Wstęp:</b> <xsl:value-of select="manual/introduction"/></td>
</tr>
</table>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td>
<b>Spis tre¶ci</b><br/>
<ul><xsl:for-each select="manual/section">
<li><xsl:value-of select="@name"/><ul>
<xsl:for-each select="item">
<li>
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="@file"/>
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each></ul>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet> |
OK, teraz wypadałoby to przeanalizować :). Na początku za pomocą dyrektywy xsl:include informujemy, że chcemy dołączyć plik man-top.xsl z naszym logiem. Następnie definiujemy szablon przetwarzania dla całego dokumentu (znaczek "/"). Jak widać, tworzenie arkusza XSL sprowadza się do wprowadzenia tagów HTML "opakowanych" w znaczniki XSL'owe :). Pierwszym takim znacznikiem, który NAPRAWDĘ wpływa na zawartość, jest <xsl:value-of>. Pozwala on wstawić nam zawartość danego znacznika, lub jego atrybutu w podane miejsce. My chcemy pobrać atrybut "title" znacznika głównego "manual", dlatego nasza ścieżka będzie wyglądała następująco: "manual/@title". Dalej natrafiamy na <xsl:call-template> z atrybutem name. Dzięki tej dyrektywie możemy w tym miejscu wstawić nasze logo, które znajduje się w szablonie "manual-header" w dołączonym wcześniej pliku "man-top.xsl". Schodząc kolejne linijki w dół natrafiamy na <xsl:for-each>. Jest to znacznik działający podobnie do pętli - pobiera kolejne tagi o podanej nazwie i wykonuje dla każdego z nich zawarty w sobie kod. My chcemy wyświetlić wszystkich autorów manuala, dlatego określamy to w ścieżce: "manual/authors/author" - pobierz wszystkie znaczniki "author". Teraz zaczynają się małe schody. Mianowicie z DTD wynika, że atrybut "email" znacznika "author" jest OPCJONALNY, a co za tym idzie, nie trzeba go podawać. Musimy zatem obsłużyć autora, który nie podał swego adresu e-mail, jak i tego, który podał. Z pomocą przychodzi nam tu konstrukcja
<xsl:choose>
<xsl:when test="warunek">
... kod, gdy waruenk jest prawdziwy...
</xsl:when>
<xsl:otherwise>
... kod, gdy warunek jest fałszywy...
</xsl:otherwise>
</xsl:choose> |
Pozwala nam ona zareagować odpowiednio na różne ewentualności, np. czy dany atrybut istnieje, czy nie. W naszym przypadku jako warunek wstawiliśmy po prostu "@email" - jeśli będzie, do autora będzie można napisać. Jeśli nie, to "qpa" :).
Dalej mamy bardzo ciekawy sposób tworzenia znaczników HTML, w któych musimy edytować także atrybuty. Po prostu deklarujemy sobie konstrukcję <xsl:element name="znacznik>zawartosc</xsl:element>. Jeśli chcemy dodać atrybut, umieszczamy wewnątrz konstrukcję <xsl:attribute name="nazwa_atrybutu">zawartosc</xsl:attribute>. Dane wstawiamy za pomocą znanego już <xsl:value-of>, przy czym musisz zwrócić uwagę na ścieżkę do taga. Jako że znajdujemy się w przetwarzanym "manual/authors/author", nie musimy tej ścieżki podawać w znacznikach XSL wewnątrz for-each'a - wystarczy więc, że wpiszemy "@email". Tak samo dalej użyliśmy kropki w nazwie, by pobrać zawartość przetwarzanego znacznika "author".
Spróbuj teraz na podstawie uzyskanych wyżej informacji przeanalizować resztę listingu do końca. Zobaczysz wtedy, że XSL jest bajecznie prosty, a możliwości ma ogromne.
Przetestujmy teraz, jak działa wyświetlanie menu. Bezpośrednio w katalogu "fillar-man" utwórz sobie plik "index.xml" i wrzuć do niego taką zawartość:
<?xml version="1.0" encoding="iso-8859-2"?>
<!DOCTYPE manual SYSTEM "dtd/man-menu.dtd">
<?xml:stylesheet type="text/xsl" href="xsl/man-menu.xsl"?>
<manual title="Przykładowy manual">
<authors>
<author email="zyxwvu@me2.pl">Tomasz Jędrzejewski</author>
<author email="waldek@kiepski.pl">Waldemar Kiepski</author>
<author>Jan Kowalski</author>
</authors>
<introduction>
Manual ten demonstruje użycie aplikacji fillar-man.
</introduction>
<section name="Instalacja">
<item file="inst1.xml">A po co to</item>
<item file="inst2.xml">Potrzebne zasoby</item>
<item file="inst3.xml">Instalacja</item>
<item file="inst4.xml">Pierwsze uruchomienie</item>
</section>
<section name="Konfiguracja">
<item file="conf1.xml">Pliki konfiguracyjne</item>
<item file="conf2.xml">Opis dyrektyw</item>
</section>
<section name="Opis funkcji">
<item file="func1.xml">Baza MySQL</item>
<item file="func2.xml">Data i czas</item>
<item file="func3.xml">XML</item>
<item file="func4.xml">Microsoft .NET</item>
</section>
</manual> |
Uruchom go w przeglądarce i co? Okazuje się, że zdefiniowaliśmy całkowicie nowy zestaw znaczników, a co więcej, dzięki XSL'owi przeglądarka WIE, jak go wyświetlić! Zmiana layoutu to teraz czysta formalność :).
man-common
Teraz przejdziemy do zagadnienia znacznie trudniejszego, niż poprzednie. Przede wszystkim tym razem struktura musi być
płynna, tzn. nie możemy na sztywno zdefiniować, że dany znacznik ma zawierać te, te i ewentualnie ten tag. Całość musimy tak zaprojektować, by można było zrobić zarówno coś takiego:
tekst tekst <b>tekst tekst </b> tekst <i> tekst <b> tekst</b></i> |
jak i coś takiego:
tekst tekst <list><e>tekst <b>tekst</b></e><e> tekst <code> kod</code></e></list> tekst <u>tekst <b> tekst </b></u> |
Zagadnienie to jest dosyć trudne - ja "strawiłem" trzy dni na znalezienie rozwiązania tego problemu. Warto go oszczędzić innym :).
Koniec gadania, zabierzmy się za pisanie. Najpierw DTD: man-common.dtd do katalogu "dtd":
<!ENTITY % inlinetags "b | i | u | br | img | link">
<!ENTITY % blocktags "p | code | table | list | quote">
<!ENTITY % Block "(%blocktags;)*">
<!ENTITY % Inline "(#PCDATA | %inlinetags;)*">
<!ENTITY % BlockOrInline "(#PCDATA | %inlinetags; | %blocktags;)*">
<!-- OPCJE DOKUMENTU -->
<!ELEMENT options (title+, description?, authors*, seealso*)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT seealso EMPTY>
<!ATTLIST seealso title CDATA #REQUIRED
href CDATA #REQUIRED>
<!ELEMENT authors (author*)>
<!ELEMENT author (#PCDATA)>
<!ATTLIST author email CDATA #IMPLIED>
<!-- SEKCJA -->
<!ELEMENT section (text+,implementation)>
<!ATTLIST section id CDATA #REQUIRED title CDATA #REQUIRED>
<!ELEMENT text %Block;>
<!-- INLINETAGS -->
<!ELEMENT b %Inline;>
<!ELEMENT i %Inline;>
<!ELEMENT u %Inline;>
<!ELEMENT br EMPTY>
<!ELEMENT img EMPTY>
<!ATTLIST img
src CDATA #REQUIRED
alt CDATA #IMPLIED
>
<!ELEMENT link (#PCDATA)>
<!-- BLOCKTAGS -->
<!ELEMENT p %Inline;>
<!ATTLIST p class CDATA #IMPLIED>
<!ELEMENT table (tr)+>
<!ATTLIST table width CDATA #IMPLIED
height CDATA #IMPLIED
bgcolor CDATA #IMPLIED
cellspacing CDATA #IMPLIED
cellpadding CDATA #IMPLIED
border CDATA #IMPLIED
style CDATA #IMPLIED
class CDATA #IMPLIED>
<!ELEMENT tr (th | td)+>
<!ATTLIST tr valign CDATA #IMPLIED >
<!ELEMENT td %BlockOrInline;>
<!ATTLIST td colspan CDATA #IMPLIED
rowspan CDATA #IMPLIED
class CDATA #IMPLIED
style CDATA #IMPLIED
width CDATA #IMPLIED
height CDATA #IMPLIED
bgcolor CDATA #IMPLIED
align CDATA #IMPLIED>
<!ELEMENT code (#PCDATA)>
<!ATTLIST code
info CDATA #REQUIRED
nbr CDATA #IMPLIED
>
<!ELEMENT list (e*)>
<!ELEMENT e %Inline;>
<!ELEMENT quote %Inline;>
<!ATTLIST quote author CDATA #IMPLIED>
<!ELEMENT implementation (part*)>
<!ELEMENT part (def+)>
<!ATTLIST part info CDATA #REQUIRED>
<!ELEMENT def %Inline;>
<!ATTLIST def for CDATA #REQUIRED> |
Hehe... to się nazywa prawdziwy DTD :). Na samym początku pojawiła nam się nowa struktura: ENTITY. Pozwala nam ona na zdefiniowanie grup znaczników pod jedną nazwą. Używamy jej w taki sposób, jak widać. Natomiast jeśli chcemy uyć takiej grupy np. w tagu ELEMENT, robimy coś takiego:
<!ELEMENT nazwa %grupa;>
Zdefiniowalimy sobie trzy główne grupy: Block, Inline, oraz BlockOrInline. Block definiuje tagi, które reprezentują logiczne części tekstu: akapity, listy, tabele, przykłady itp. Inline natomiast to zestaw tagów powodujących graficzną zmianę reprezentacji tekstu: pogrubienie, przełamanie wiersza, wstawienie obrazka, czy określenie, że dany tekst jest adresem URL :). Mamy także BlockOrInline, czyli zarówno zwykły tekst, bez żadnych duperszmitów, tagi Inline, oraz tagi Block. Po prostu: wszystko w jednym. Dalej mamy już określenie logicznej struktury dokumentu. Oto opis:
manpage
|
+- options
| |
| +- title+ [tytuł]
| +- description? [krótki opis]
| +- seealso (title - nazwa zasobu; href - adres) [zobacz także]
| +- authors [lista autorów danej strony]
| |
| +- author (email - e-mail) [pojedynczy autor]
+- section (wymagane: id - identyfikator sekcji; title - tytuł sekcji) [dokument dzieli się na sekcje]
|
+- text [właściwy tekst objaśniający]
+- implementation [implementacja omawianego zagadnienia
| |
| +- part (info - co objaśniamy) [pojedyncza partia implementacji, np. jedna klasa, lub pojedynczy zbiór]
| |
| +- def (for - opis składni) [pojedyncza definicja np. funkcji, metody itp.] |
A oto opis tagów Inline i Block:
Inline:
- b - pogrubienie. Użycie: <b>tekst</b>
- i - kursywa. Użycie: <i>tekst</i>
- u - podkreślenie. Użycie: <u>tekst</u>
- img - obrazek. Użycie: <img src="plik.jpg" alt="Informacja"/>
- link - kursywa. Użycie: <link>http://www.webcity.pl</link>
- br - przełamanie wiersza. Użycie: tekst <br/> tekst
Block:
- p - akapit. Użycie: <p>tekst</p>
- code - przykładowy kod. Użycie: <code nbr="1" info="Użycie znacznika CODE">blebleble</code>
- quote - cytat. Użycie: <quote author="Kartezjusz">Myślę, więc jestem</quote>
- list - lista. Użycie: <list><e>Element 1</e><e>Element 2</e></list>
- table - tabela. Użycie: tak, jak w HTML'u
Gdy mamy już omówioną strukturę naszego dokumentu, przystąpmy do kodzenia. Utwórz w katalogu "xsl" plik "man-common.xsl". Kolejne partie kodu będą dotyczyły właśnie jego.
<?xml version="1.0" encoding="iso-8859-2"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="man-top.xsl"/>
<!-- Struktura pojedynczej strony -->
<xsl:template match="/">
<html>
<head>
<title>
<xsl:value-of select="options/title"/>
</title>
</head>
<body>
<a name="up"/>
<xsl:call-template name="manual-header"/>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#EEEEEE">Dział: <b><xsl:value-of select="manpage/options/title"/></b> [<i><xsl:value-of select="manpage/options/description"/></i>] :: <a href="index.xml">Powrót na stronę główn±</a></td>
</tr>
</table>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td><b>Autorzy sekcji:</b></td>
</tr>
<xsl:for-each select="manpage/options/authors/author">
<xsl:choose>
<xsl:when test="@email">
<tr>
<td>
<xsl:element name="a">
<xsl:attribute name="href">
mailto:<xsl:value-of select="@email"/>
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</td>
</tr>
</xsl:when>
<xsl:otherwise>
<tr>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</table>
<br/>
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#EEEEEE"><b>Zobacz także:</b></td>
</tr>
<xsl:for-each select="manpage/options/seealso">
<tr>
<td bgcolor="#EFEFEF">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="@href"/>
</xsl:attribute>
<xsl:value-of select="@title"/>
</xsl:element>
</td>
</tr>
</xsl:for-each>
</table>
<br/> |
Pierwszy fragment kodu nie wymaga szczególnej analizy - jest to praktycznie lekko zmodyfikowana wersja pliku man-menu.xsl, a niektóre partie kodu zostały z niego nawet skopiowane metodą ctrl+c i ctrl+v :). Przyjrzyjmy się za to lepiej drugiej części szablonu głównego:
<xsl:for-each select="manpage/section">
<h1><i>
<xsl:element name="a">
<xsl:attribute name="name">
<xsl:value-of select="@id"/>
</xsl:attribute>
</xsl:element>
<xsl:value-of select="@title"/>
</i></h1>
<xsl:for-each select="text/.">
<xsl:apply-templates/>
</xsl:for-each>
<br/>
<xsl:apply-templates select="implementation"/>
<br/>
<a href="#up">Do góry</a>
</xsl:for-each>
</body>
</html>
</xsl:template> |
Tu mamy już trochę ciekawszy kod - mianowicie przetwarzanie kolejnych sekcji. Zobacz, jak ciekawie poradziłem sobie z problemem płynności - kazałem XSL'owi przetwarzać zawartość znacznika TEXT, lecz nie wyświetlam go bezpośrednio. Zamiast tego wywołuję dyrektywę xsl:apply-templates. Co ona robi, już wyjaśniam. Otóż zaraz będziemy definiować w osobnych szablonach obsługę każdego znacznika Block i Inline. Wywołując apply-templates rozkaujemy przeglądarce, by wybierała odpowiednie przetwarzanie dla każdego znacznika z listy dostępnych (poprzez wywołanie szablonu), a ten już zajmie się wyświetleniem wszystkiego. Przejdźmy więc dalej:
<!-- Tekst nie znajduj±cy się w podznacznikach [fragment pochodzi z arkusza stylów manuala Apache]-->
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="@*|*|text()"/>
</xsl:copy>
</xsl:template> |
A tu mamy dziwoląga :) - nie jest to szablon dla realnego znacznika, a dla tekstu w znaczniku głównym. Przykładowo gdy mamy taki ciąg: "tekst tekst tekst <b>pogrubione pogrubione pogrubione</b> tekst tekst", szablon ten zostanie wywołany najpierw dla pierwszych trzech wyrazów "tekst", potem sterowanie przejdzie pod kontrolę szablonu odpowiedzialnego za pogrubienie, a potem znów do tego. I to jest cała esencja "płynności" znaczników. Idźmy więc dalej:
<!-- akapit -->
<xsl:template match="p">
<xsl:element name="p">
<xsl:attribute name="align">
left
</xsl:attribute>
<xsl:for-each select=".">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- pogrubienie -->
<xsl:template match="b">
<xsl:element name="b">
<xsl:for-each select=".">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- Kursywa -->
<xsl:template match="i">
<xsl:element name="i">
<xsl:for-each select=".">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- Podkreślenie -->
<xsl:template match="u">
<xsl:element name="u">
<xsl:for-each select=".">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:element>
</xsl:template> |
Kodu tego raczej omawiać nie muszę - jest on bardzo prosty do zrozumienia.
Dalej mamy szablony odpowiadające za przetwarzanie przykładowych kodów i cytatów. Jeśli nie załapałeś za bardzo składni tych znaczników podczas ich omawiania, możesz sobie zobaczyć, gdzie wstawiany jest jaki parametr:
<!-- Przykładowy kod -->
<xsl:template match="code">
<table border="0" width="100%" bgcolor="#EFEFEF" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td><i>Przykład <xsl:value-of select="@nbr"/>: <xsl:value-of select="@info"/></i></td>
</tr>
<tr>
<td>
<xsl:element name="pre">
<xsl:value-of select="."/>
</xsl:element>
</td>
</tr>
</table>
</xsl:template>
<!-- Cytat -->
<xsl:template match="quote">
<table border="0" width="40%" bgcolor="#EEEEEE" style="border: 1 solid #000000" cellspacing="0" cellpadding="0">
<tr>
<td>
<xsl:element name="i">
<xsl:value-of select="."/>
</xsl:element>
<div align="right"><xsl:value-of select="@author"/></div>
</td>
</tr>
</table>
</xsl:template> |
Dalej mamy z kolei przetwarzanie linków, obrazków, przełamań wiersza i listy. Kod ten również nie wymaga zbyt dogłębnej analizy:
<!-- Szybkie tworzenie listy -->
<xsl:template match="list">
<ul>
<xsl:for-each select="e">
<li>
<xsl:apply-templates/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
<!-- Zamienia podany tekst w link -->
<xsl:template match="link">
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- Obrazek -->
<xsl:template match="img">
<xsl:element name="img">
<xsl:attribute name="src">
<xsl:value-of select="@src"/>
</xsl:attribute>
<xsl:attribute name="alt">
<xsl:value-of select="@alt"/>
</xsl:attribute>
<xsl:attribute name="border">
0
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- Zejście wiersz niżej -->
<xsl:template match="br">
<xsl:element name="br"/>
</xsl:template> |
Ale następny już pewnego omówienia wymaga - chodzi mianowicie o rysowanie tabel HTML:
<!-- Obsługa zwykłego znacznika <table>, którego przeznaczenie znaj± chyba wszyscy :) -->
<xsl:template match="table">
<xsl:element name="table">
<xsl:if test="@width">
<xsl:attribute name="width"><xsl:value-of select="@width"/></xsl:attribute>
</xsl:if>
<xsl:if test="@height">
<xsl:attribute name="height"><xsl:value-of select="@height"/></xsl:attribute>
</xsl:if>
<xsl:if test="@border">
<xsl:attribute name="border"><xsl:value-of select="@border"/></xsl:attribute>
</xsl:if>
<xsl:if test="@bgcolor">
<xsl:attribute name="bgcolor"><xsl:value-of select="@bgcolor"/></xsl:attribute>
</xsl:if>
<xsl:if test="@style">
<xsl:attribute name="style"><xsl:value-of select="@style"/></xsl:attribute>
</xsl:if>
<xsl:if test="@class">
<xsl:attribute name="class"><xsl:value-of select="@class"/></xsl:attribute>
</xsl:if>
<xsl:if test="@cellspacing">
<xsl:attribute name="cellspacing"><xsl:value-of select="@cellspacing"/></xsl:attribute>
</xsl:if>
<xsl:if test="@cellpadding">
<xsl:attribute name="cellpadding"><xsl:value-of select="@cellpadding"/></xsl:attribute>
</xsl:if>
<!-- Zawartosc tabeli -->
<xsl:for-each select="tr">
<xsl:element name="tr">
<xsl:if test="@valign">
<xsl:attribute name="valign"><xsl:value-of select="@valign"/></xsl:attribute>
</xsl:if>
<xsl:for-each select="td">
<xsl:element name="td">
<xsl:if test="@width">
<xsl:attribute name="width"><xsl:value-of select="@width"/></xsl:attribute>
</xsl:if>
<xsl:if test="@height">
<xsl:attribute name="height"><xsl:value-of select="@height"/></xsl:attribute>
</xsl:if>
<xsl:if test="@align">
<xsl:attribute name="align"><xsl:value-of select="@align"/></xsl:attribute>
</xsl:if>
<xsl:if test="@bgcolor">
<xsl:attribute name="bgcolor"><xsl:value-of select="@bgcolor"/></xsl:attribute>
</xsl:if>
<xsl:if test="@style">
<xsl:attribute name="style"><xsl:value-of select="@style"/></xsl:attribute>
</xsl:if>
<xsl:if test="@class">
<xsl:attribute name="class"><xsl:value-of select="@class"/></xsl:attribute>
</xsl:if>
<xsl:if test="@colspan">
<xsl:attribute name="colspan"><xsl:value-of select="@colspan"/></xsl:attribute>
</xsl:if>
<xsl:if test="@rowspan">
<xsl:attribute name="rowspan"><xsl:value-of select="@rowspan"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template> |
Pojawiła nam się tutaj kolejna instrukcja: <xsl:if test="warunek">zawartosc</xsl:if>. Jest to uproszczona wersja konstrukcji xsl:choose - w przeciwieństwie do tamtej nie wymaga definiowania, co mamy zrobić, gdy warunek nie jest spełniony. Tutaj użyłem go do określenia, czy użytkownik zdefiniował jakiś atrybut - jeśli tak, tworzę go także w kodzie wynikowym HTML.
Dobra, ostatnia część systemu została nam już tylko do skręcenia: obsługa znacznika <implementation>
<!-- Sekcja implementacji - czyli opis wszystkich składowych omawianego podststemu -->
<xsl:template match="implementation">
<b><h3>Implementacja</h3></b>
<xsl:for-each select="part">
<i><xsl:value-of select="@info"/></i>
<table border="1" width="100%">
<xsl:for-each select="def">
<tr>
<td bgcolor="#999999"><font color="#FFFFFF"><xsl:value-of select="@for"/></font></td>
</tr>
<tr>
<td bgcolor="#EEEEEE"><xsl:value-of select="."/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet> |
Mamy tu najpierw przetwarzanie wszystkich znaczników PART, a każdy z nich przetwarza dodatkowo podznaczniki DEF.
Przydałby się jeszcze jakiś przykładzik wykorzystania tego do utworzenia dokumentacji:
<?xml version="1.0" encoding="iso-8859-2"?>
<!DOCTYPE manpage SYSTEM "dtd/man-common.dtd">
<?xml:stylesheet type="text/xsl" href="xsl/man-common.xsl"?>
<manpage>
<options>
<title>Strona testowa manuala</title>
<description>Demonstruje użycie aplikacji fillar-man do tworzenia pojedycznych stron</description>
<seealso title="Literatura" href="book.xml"/>
<authors>
<author email="zyxwvu@me2.pl">Tomasz Jędrzejewski</author>
</authors>
</options>
<section id="section_1" title="Sekcja 1">
<text>
<p>The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.</p>
<code nbr="1" info="Demonstracja znacznika CODE">
int main(){
std::cout<<"Hello world\n";
}
</code>
<quote author="Kartezjusz">Myślę, więc jestem</quote>
</text>
<implementation>
<part info="Funkcje standardowe">
<def for="set_names($text, $text2)">opis deklaracji</def>
<def for="set_names($text, $text2)">opis deklaracji</def>
<def for="set_names($text, $text2)">opis deklaracji</def>
<def for="set_names($text, $text2)">opis deklaracji</def>
</part>
</implementation>
</section>
</manpage> |
Zakończenie
Ufff... pisania było dużo, ale się opłaciło. Mam nadzieję, że artykuł ten wzbudzi zainteresowanie XML'em jako językiem nie tylko do przechowywania danych, ale także do tworzenia stron WWW i robienia tysięcy innych rzeczy. Pisałem go także z myślą o rozbudzeniu forum Webcity poświęconego XML'owi. Pamiętaj tylko o dwóch rzeczach:
- Jeśli będziesz chciał wykorzystać ten kod, to wiedz, że możesz go modyfikować, lecz w komentarzach wspomnij moje imię jako autora systemu :). Tak jest z resztą ze wszystkimi kodami z moich artykułów.
- Kopiowanie tego artykułu jest zabronione bez zgody mojej i serwisu Webcity.pl, jak i plagiatowanie.
Tą nutą kończę najdłuższy w mojej dotychczasowej karierze pisarskiej artykuł. Powodzenia w przygodach z XML'em! :)