headerbanner

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

Nyní již máme validní podkladový XML dokument. HTML kód z něj vytvoříme XSLT transformací. Podobně jako u validace, i nyní budeme potřebovat další XML soubor, tentokrát s elementy z dalšího jmenného prostoru, označovaného xsl. Elementy z tohoto jmenného prostoru používá XSLT procesor jako instrukce pro zpracování, elementy z jiného prostoru (resp. bez prostoru) zpravidla kopíruje na výstup.

XSL dokument definuje, jak naložit s elementy podkladového XML dokumentu, co pustit či přidat na výstup. Program v podstatě prochází XSL dokument a řídí se jednotlivými příkazy. První příkaz je "zachyť kořenový uzel XML dokumentu a aplikuj na něj tuto šablonu". V komentářích pak najdete popis, co XSLT procesor postupně dělá.

Všimněte si HTML elementů, například NAV. Pokud ho chcete vypisovat na výstup, musíte v XSL dokumentu vypsat otvírací i koncovou značku. XSL dokument musí být validním XML dokumentem, takže nelze tímto způsobem rovnou vypsat třeba neuzavřené HTML 5 tagy META a pod.

 
<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="*"/>
 
 
<!--zachytí kořen (ne uzel, ale kořen - tedy před prvním uzlem dokumentu) -->
<xsl:template match="/" >
    <!--když existují nějaké položky menu, pracuj, jinak nedělej nic -->
    <xsl:if test="/menu/menugroup/menuitem/*">
      <!--hledej template pro první dítě kořenu (nejvyšší uzel), tedy pro menu -->
      <xsl:apply-templates />
    </xsl:if> 
</xsl:template>
 
<!--předcházející příkaz apply-templates míří sem na nejvyšší uzel -->
<xsl:template match="menu" >  
    <nav> <!--vypíše startovací tag pro HTML 5 (uzavře do tagu celé menu) -->
    <!--hledej template pro dítě menu, tedy pro menugroup -->
    <xsl:apply-templates >
    <xsl:with-param name="level" select="0" />
    </xsl:apply-templates >
    </nav><!--uzavře menu (díky předchozímu apply-templates se toto vypíše až po projití celého stromu xml dokumentu)  -->
</xsl:template>
 
<xsl:template match="menugroup" >  
  <xsl:param name="level" /> 
  <ul> <!--vypíše začátek menu nebo podmenu - vždy uzavřeno v ul -->
  <!--následné apply-templates bude hledat template pro dítě menugroup, tedy pro menuitem -->
  <!--pokud bychom potřebovali pomocí tříd označit i úroveň menu,-->
  <!--pomohl by nám parametr level, který ovšem nakonec nepoužiji,-->
  <!--je zde jen pro ukázku vytvoření a předávání parametrů -->
  <xsl:apply-templates >
    <!--nyní bude parametr level definován v těle elementu with-param, -->
    <!--a to pomocí xsl:value-of, -->
    <!--v jednodušších případech lze definovat v atributu select -->
    <xsl:with-param name="level"> 
       <xsl:choose>
          <!--když je otcem tohoto elementu element menu, -->
          <!-- jde o nultou úroveň, jinak zvyš o jedničku -->
          <xsl:when test="name(..)='menu'">
            <xsl:value-of select="0"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$level+1"/>
          </xsl:otherwise>
      </xsl:choose>
    </xsl:with-param>
   </xsl:apply-templates ><!--v tomto okamžiku aplikuj další template -->
  </ul>
</xsl:template>
 
<xsl:template match="menuitem" >
  <!--parametr předaný z vyšší úrovně musí být deklarovaný na začátku template -->
  <xsl:param name="level"/>
  <!--hledej template pro elementy uvnitř elementu menuitem -->
  <!--taková template bude nalezena jen pro inanchor -->
  <!--pro ostatní nalezne obecnou template *, která nevypíše nic -->
  <!--elementy url, title a ostatní nebudou tedy vypisovány -->
  <xsl:apply-templates >
    <!--předej dále úroveň menu (ve výstupu nakonec není použita, je to jen ukázka) -->
    <xsl:with-param name="level" select="$level" />
    <!--předej dále parametr označující že to není první položka menu ve skupině menugroup -->
    <!--parametr obsahuje předchozí menuitem ve stejné menugroup, takže když jde o první menuitem -->
    <!--zůstane prázdný a pak ho můžeme testovat - pro prázdný vložíme class="first"-->
    <xsl:with-param name="nofirst" select="preceding-sibling::menuitem[1]" />
    <!--obdoba předchozího, je prázdný když jde o poslední menuitem-->
    <xsl:with-param name="nolast" select="following-sibling::menuitem[1]" />
  </xsl:apply-templates >
</xsl:template>
 
<!--nejdůležitější šablona-->
<!--položka inanchor zvolena proto, že nesmí být prázdná-->
<!--ostatní (title atd.) mohou úplně chybět,-->
<!-- pak je tento element menuitem jen oddělovačem, nikam neodkazuje  -->
<xsl:template match="inanchor" >
  <xsl:param name="level" />  <!--nevyužívá se  -->
  <xsl:param name="nofirst"/>
  <xsl:param name="nolast"/>
  <li> <!--otvírací značka - bude vždy, i když uvnitř nemusí být tag A  -->
    <!--když jsem v první menuitem ve skupině menugroup, nastavím class (může se hodin pro design) -->
    <xsl:if test="not($nofirst)">  
      <xsl:attribute name="class">
        <xsl:value-of select="'first'" />
      </xsl:attribute>
    </xsl:if>  
    <!--když jsem v poslední menuitem ve skupině menugroup, nastavím class (může se hodin pro design)  -->
    <xsl:if test="not($nolast)">  
      <xsl:attribute name="class">
        <xsl:value-of select="'last'" />
      </xsl:attribute>
    </xsl:if>
    <!-- obsah LI podle toho, zda má obsahovat A nebo ne -->
    <xsl:choose>
      <!-- když v je menuitem neprázdný element url -->
      <xsl:when test="normalize-space(../url)">
        <a> <!-- počáteční značka tagu A -->      
           <xsl:attribute name="href">
            <!-- obsah atributu href, k němu přidána koncovka .html -->
            <xsl:value-of select="../url" /><xsl:value-of select="'.html'" />
          </xsl:attribute>
          <!-- hledej template pro dítě elementu inanchor  -->
          <!-- ale jelikož dítětem je pouze text, vypíše tento text  -->
          <xsl:apply-templates />  
        </a><!-- uzavři tag -->
      </xsl:when>
      <!-- v opačných případech, když je url prázdné - bude to neodkazující oddělovač -->
      <xsl:otherwise>
          <!-- hledej template pro dítě elementu inanchor  -->
          <!-- ale jelikož dítětem je pouze textový obsah, nehledá šablonu a vypíše tento text  -->
          <xsl:apply-templates />
      </xsl:otherwise>
    </xsl:choose>
  </li><!-- uzavři tag LI -->
</xsl:template>
 
<!-- pokud by neexistovala následující šablona pro všechny elementy, -->
<!-- které nemají vlastní šablonu (respektive nejsou zachyceny pomocí match=...) -->
<!-- pak by se vypsaly obsahy těchto elementů -->
<!-- například TITLE, DESC atd. -->
<xsl:template match="*" />
</xsl:stylesheet >  
 

Nyní musíme nějak "spárovat" XSL a vstupní XML, abychom dostali výstup. To opět provedeme pomocí PHP skriptu. Všimněte si, že objekty třídy DOMDocument vytváříme z obou souborů, jak XML, tak XSL. XSL je totiž platný XML dokument.

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

Menu máme hotové, ale proč tolik práce, když jsme to mohli mít dávno ručně? Nebylo to jen kvůli pochopení XSLT. Jak s využitím uvedeného generovat kostru jednoduchého statického webu, uvidíte dále.