headerbanner

Lekce XSLT transformace a XML Schema validace, aneb jak v PHP vytvořit menu z XML (4)

Menu máme hotové, ale abychom využili vše, co máme v podkladovém XML souboru, potřebujeme vygenerovat příslušnou hlavičku pro každou stránku s tagy TITLE atd. Nejprve to trochu přeženu a z "cvičných důvodů" vygeneruji místo samotné hlavičky celou HTML stránku, aby byl vidět ucelený výsledek.

Vytvoříme novou šablonu pro transformaci, jejímž výstupem bude korektní HTML 5 stránka, zatím jen se správnou hlavičkou bez menu. Tentokrát ale použiji místo opakovaného "apply-templates" jiný způsob. Zachytíme kořen XML a pak z XML pomocí for-each vybereme a pomocí value-of vypíšeme jen určité elementy, resp. jejich hodnoty. Nebudeme tedy pro jednotlivé elementy psát zvláštní template.

 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" version="1.0" encoding="utf-8" />
<!-- omit-xml-declaration="yes" zabrání generování instrukce pro zpracování XML dokumentu--> 
 
<!-- generuje výstup bez zbytečných mezer 
<xsl:strip-space  elements="*"/>          -->
 
<!-- variable lze pouze jednou nastavit, bude se měnit z vnějšku  -->
<!-- bude v ní url řetězec (bez přípony .html) právě vytvářené stránky  -->
<!-- zatím tento xsl soubor "natvrdo" generuje vstupní stranu webu pro url=index   -->
<xsl:variable name="pageurl" select="'index'" />
 
<!-- proměnná pro znak nového řádku  -->
<xsl:variable name='nl'><xsl:text>
</xsl:text></xsl:variable>
 
 
<xsl:template match="/">
 
  <!-- pokud by se v menu opakoval odkaz na stejnou stránku, proběhl by
  cyklus for-each víckrát. Proto vybereme jen první zachycený uzel. Lze to
  udělat mnoha způsoby, například tímto:   -->
  <xsl:for-each select ="//menuitem[url=$pageurl and not(url=preceding::url)]">
 
    <!-- vypíšeme hlavičku stránky kódované v HTML 5  -->      
    <!-- entita &#60; nahrazuje znak <, který se v XML nesmí nekódovaně vyskytnout -->
    <!-- disable-output-escaping pak způsobí, že se na výstup vypíše skutečná podoba znaku  -->
    <!-- abych nemusel přemýšlet, co kde může a nemůže být, používám entity i pro " nebo >  -->
    <xsl:value-of select = "'&#60;!doctype html>'"  disable-output-escaping="yes" />
    <html> <!-- vypíše HTML tag  -->  
 
       <xsl:attribute name="lang">
        <xsl:value-of select="langs" />
      </xsl:attribute>
 
      <xsl:value-of select="$nl"/><!-- zapíše znak nového řádku  -->
      <head><!-- kvůli sekce head to všechno nyní děláme  -->
        <title><xsl:value-of select = "title"/></title>  
         <!-- HTML 5 není XML (respektive SGML), tagy nemusí být uzavřené  -->
        <xsl:value-of select = "'&#60;meta name=&#34;description&#34; content=&#34;'" disable-output-escaping="yes" />
        <xsl:value-of select = "concat(desc,'&#34;&#62;',$nl)" disable-output-escaping="yes" />
 
        <xsl:value-of select = "'&#60;meta name=&#34;keywords&#34; content=&#34;'" disable-output-escaping="yes" />
        <xsl:value-of select = "concat(key,'&#34;&#62;',$nl)" disable-output-escaping="yes" />
 
        <xsl:value-of select = "'&#60;meta charset=&#34;UTF-8&#34; &#62;'" disable-output-escaping="yes" />
        <xsl:value-of select = "$nl" />  
      </head>
      <body>
      <!-- tady stránku později "roztrhneme" a budeme vkládat menu, 
       případně patičku nebo jiný "prefabrikovaný" kód  -->    
      </body>      
    </html>
 
 
  </xsl:for-each>
</xsl:template >
 
 
</xsl:stylesheet>     
 

Výše uvedený obsah uložíme například do souboru onepage.xsl. Pak použijeme stejný PHP skript pro transformaci jako dříve, jen změníme jméno xsl šablony:

 
<?php
$xml = new DomDocument();
$xml->load("menu.xml", 
     LIBXML_NOENT|LIBXML_DTDLOAD|LIBXML_DTDATTR);
 
 
$xsl = new DomDocument();
$xsl->load("onepage.xsl", 
     LIBXML_NOENT|LIBXML_DTDLOAD|LIBXML_DTDATTR);
 
$proc = new xsltprocessor();
$proc->importStylesheet($xsl);
echo $proc->transformToXML($xml);
 

Nezapomeňte se podívat na zdrojový text vytvořené stránky, šablona generuje jen hlavičku, ne viditelný obsah (až na tag TITLE).

Všimněte si v .xsl souboru řádku s proměnnou variable. Její hodnota je nastavena na "index" a určuje, že se má vygenerovat hlavička pro vstupní stránku webu. Proměnnou však můžeme nastavovat zvenku z PHP skriptu a tím můžeme generovat všechny v XML souboru definované stránky. POZOR - v tom případě ale nesmí být proměnná v XSL dokumentu deklarována.

Než se ke generování stránek dostaneme, potřebujeme z podkladového XML dostat obsah všech url elementů. Po jednom je pak budeme vkládat do zmíněné variable. Obsahy url zjistí následující šablona v souboru allurl.xsl:.

 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" version="1.0" encoding="utf-8" />
<!-- omit-xml-declaration="yes" zabrání generování instrukce pro zpracování XML dokumentu--> 
 
<!-- generuje výstup bez zbytečných mezer  -->
<xsl:strip-space  elements="*"/>         
 
<xsl:template match="/" >
<!-- select="//menuitem" znamená selektuj všechny uzly menuitem  -->
<!-- část [url/text() and not(url=preceding::url)] vylučuje  -->
<!-- "slepé" položky menu bez url sloužící jako oddělovač -->
<!-- a selektuje pouze jediný výskyt stejnojmenného odkazu -->
<!-- (je to jen  kontrola, v menu je zpravidla odkaz na konkrétní stranu pouze jednou) -->
    <xsl:for-each select ="//menuitem[url/text() and not(url=preceding::url)]">
 
     <!-- když je v elementu url neprázdný řetězec  -->
     <xsl:if test="url[text() and not(url=preceding::url)]"> 
        <!-- do výstupu vlož obsah url a za to znak ~  -->
        <xsl:value-of select = "concat(url,'~')" />
      </xsl:if>
    </xsl:for-each>
</xsl:template> 
</xsl:stylesheet >  
 

Nyní v dříve použitém transformačním PHP skriptu změňte jen jméno šablony na allurl.xsl a dostanete výsledek - seznam všech použitých url oddělený znakem ~.

Všechny části jsou funkční, takže napíšeme skript, který je využívá. Ze seznamu vytvoříme pole adres a budeme je procházet v cyklu. Při každém průchodu změníme proměnnou pageurl a vygenerovanou stranu uložíme do souboru:

 
<?php
//načíst XML dokument s popisem stránek
$xml = new DOMDocument();
$xml->load("menu.xml", 
     LIBXML_NOENT|LIBXML_DTDLOAD|LIBXML_DTDATTR);
 
//načíst šablonu pro získání položek menu 
$xslmenu = new DOMDocument();
$xslmenu->load("allurls.xsl", 
     LIBXML_NOENT|LIBXML_DTDLOAD|LIBXML_DTDATTR);
 
//načíst šablonu pro získání kódu stránky 
$xslpage = new DOMDocument();
$xslpage->load("onepage.xsl", 
     LIBXML_NOENT|LIBXML_DTDLOAD|LIBXML_DTDATTR);     
 
//nyní máme načten vstupní XML soubor i obě šablony
 
 
//získáme položky menu - tedy obsahy všech elementů url   
$procmenu = new xsltprocessor();
$procmenu->importStylesheet($xslmenu);
 
//vytvoříme pole se seznamen url (bez koncového .html)
$menunames= explode('~', $procmenu->transformToXML($xml));
unset($procmenu);
 
//připravíme si nový procesor se šablonou 
$procpage = new xsltprocessor();
$procpage->importStylesheet($xslpage);
 
//v cyklu projdeme všechnny neprázdné hodnoty elementů url
foreach ($menunames as $value) {
  //díky koncovému ~ dostaneme z dřívejšího explode i jeden prázdný prvek pole
        //tak to ošetříme
        $value=trim($value);
  if ($value){
    echo $value.'<br>';//pro kontrolu zobrazíme url vytvářených stran
    $procpage->removeParameter('', "pageurl");
    $procpage->setParameter('', "pageurl", $value);
    //uložení strany do souboru ('file:///' umísťuje soubory do hlavního adresáře disku)
    $procpage->transformToURI($xml, 'file:///' . $value.".html");
    //jiný možný způsob uložení do souboru:
    //file_put_contents($value.".html", $procpage->transformToXML($xml)) ;
  }
 
}
 

Pomocí tohoto PHP skriptu můžeme generovat všechny strany statického webu, před tím ale nezapomeňte vymazat dekladaci proměnné pageurl v souboru onepage.xsl. Vymažte či okomentujte řádek s kódem:

 
<xsl:variable name="pageurl" select="'index'"  />
 

Výsledkem je vygenerování příslušného počtu souborů s hlavičkami, ovšem ještě bez menu. Jak ale dostat menu dovnitř vygenerovaných souborů?

Mohli bychom v XSLT použít cyklus pro generování menu uvnitř cyklu pro generování stránky. Jenže "cyklus uvnitř cyklu" by nebyl moc přehledný. Bez požadavku na nějakou zvláštní eleganci si pomůžeme roztržením kódu stránky na dvě části, v první fázi pomocí XSLT vygenerujeme u každé strany hlavičku a otevírací tag BODY, pak vložíme vygenerované menu a nakonec kód strany uzavřeme.

Pokud ovšem chceme přece jen trochu víc "automatizace" a předpokládáme, že "systém" použijeme vícekrát, vyplatí se pracoval se šablonou. Když stránku stylujeme, stejně nějaký kód vytvoříme, takže pokud ho využijeme jako šablonu pro všechny strany, ušetříme si mnoho Ctrl-C, Ctrl-V. To ovšem příště.