PHP/XSLT und PHP

Aus Mikiwiki
< PHP
Zur Navigation springen Zur Suche springen

XSLT und PHP

XSLT steht in PHP 5 als Teil der Bibliothek "libxml2" zur Verfügung, benötigt unter Linux allerdings die Übersetzung mit "--with-dom-xslt[=verzeichnis]"; unter Ubuntu 8.04 kann das XSL-Modul für PHP5 über das Paket "php5-xsl" installiert werden - danach ist ein Neustart von des Apache-Webservers notwendig, worauf in der Ausgabe der Funktion "phpinfo" der neue Abschnitt "xsl" erscheint.

Für die folgenden Beispiele werden drei Dinge benötigt:

  • Eine XML-Datei, welche umzuwandelnde Daten enthält.
  • Eine XSLT-Datei, damit es auch etwas umzuwandeln gibt.
  • PHP, um den Vorgang anzustossen.

Angenommen, auf einer Homepage werden umfangreiche Informationen im Rahmen eines Content Management Systems angeboten. Die Benutzer können sich die Daten auf den Seiten ansehen, die auch Links enthalten. Der Webbrowser stellt diese Links unterstrichen dar und verzweigt zur verlinkten Seite. Will ein Benutzer die Seite ausdrucken, so erscheint anstatt eines lesbaren Links lediglich ein unterstrichener Schriftzug, sodass der ursprüngliche Hinweis auf die Zielseite verlorengeht.

Die Daten sollen nun nicht in HTML, sondern in einem eigenen XML-Format aufbereitet werden. Als Mustertext wird die folgende Auflistung verwendet.

<?xml version="1.0" encoding="utf-8" ?>
<!-- Name     source.xml -->
<document>
  <title>Das ist ein Test</title>
  <author>Peter Meier</author>
  <text>
    Dies ist ein Text, der überall erscheinen soll. Er kann auch
    <link href="http://de.wikipedia.org" source="Q2">Hyperlinks</link> enthalten.
    <link href="http://www.php.net" source="Q4">Weitere Informationen</link> über PHP finden sich im Internet
  </text>
</document>

Aus dieser Datenquelle sollen nun zwei Versionen aufbereitet werden: Die Anzeige der HTML-Seite und eine Version für den Druck. Vor der Entwicklung des XSLT-Skripts wird die Verarbeitungsinstanz in PHP erstellt. Die folgende Auflistung zeigt, wie das mit den DOM-Erweiterungen aussieht. Die Anwendung der KLassen verlangt nur noch wenige Codezeilen.

<?php
## Name     dom_transform.php

$dom    = new DomDocument();
## Laden der zu verarbeitenden XML-Datei.
$dom->load("data/source.xml");

$xsl    = new DomDocument();
## Laden der Stylesheet-Datei mit den Umwandlungsanweisungen.
$xsl->load("data/phpnews.xslt");

## Erstellen eines XSLT-Prozessors, der die Umwandlung durchführt.
$xpr    = new XsltProcessor();
$xsl    = $xpr->importStylesheet($xsl);

$output = $xpr->transformToDoc($dom);
echo $output->saveXML();
?>

Zur besseren Erklärung der Arbeitweise ist ein Blick auf die beiden XSLT-Dateien notwendig. Zuerst das "Startskript" - also die standardmässig verwendete Datei.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Name     phpnews.xslt -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- Umwandlung der in der Ausgangsdatei als UTF-8 kodierten Umlaute in
       webbrowsertauglichem HTML-Format -->
  <xsl:output encoding="html" />
  <xsl:param name="sendfile" />
  <!-- Startpunkt ist die erste Vorlage, die auf das Stammelement der XML-Datei 
       ("document") reagiert -->
  <xsl:template match="document">
    <html>
    <head><title>News Site</title></head>
    <basefont face="Verdana" />
    <body>
    <!-- Als erstes Element wird dynamisch ein Anchor-Tag erstellt, das später
         zum Umschalten auf die Druckansicht benötigt wird.
         Es beginnt mit einer Elementdefinition. -->
    <xsl:element name="a">
      <!-- Es folgt das Attribut "href". -->
      <xsl:attribute name="href">
        <!-- Da das Skript universell sein soll, wird das Ziel ("ausgabe.php")
             nicht fest programmiert, sondern durch Übergabe eines Parameters. -->
        <xsl:value-of select="$sendfile" />
        <xsl:text>?print</xsl:text>
      </xsl:attribute>
      <xsl:attribute name="target">blank</xsl:attribute>
      <xsl:text>Drucken?</xsl:text>
    </xsl:element>
    <p />
    <xsl:call-template   name="title" />
    <xsl:apply-templates select="text" />
    <xsl:call-template   name="author" />
    </body>
    </html>
  </xsl:template>
  <xsl:template match="text">
    <p><xsl:apply-templates /></p>
  </xsl:template>
  <xsl:template name="title">
    <h2><xsl:value-of select="title" /></h2>
  </xsl:template>
  <!-- Umsetzung der Pseudo-Tags "link" aus der XML-Datei in HTML-konforme
       Links. Folgende Vorlage reagiert an jeder beliebigen Stelle im Dokument
       auf das Tag "link". Dann wird an dieser Stelle das HTML-Tag "a"
       erzeugt. Das Attribut "href" wird aus dem gleichnamigen Attribut der
       Quelle gespiesen:   href="{@href}" -->
  <xsl:template match="*/link">
    <a class="link" href="{@href}">
      <!-- Inhalt des "link"-Tags wird unverändert Inhalt des "a"-Tags. -->
      <xsl:value-of select="." />
    </a>
  </xsl:template>
  <xsl:template name="author">
    <hr noshade="noshade" />
    <xsl:value-of select="author" />
    <hr noshade="noshade" />
  </xsl:template>
</xsl:stylesheet>

Als Ergebnis des Vorgangs entsteht nun eine HTML-Seite, die wie folgt beginnt. Mangels ausdrücklich erzeugter Zeilenumbrüche erscheint der Text sehr kompakt. Die hier nicht verwendete XSLT-Anweisung "<xsl:output>" hilft bei der Erstellung lesbarerer Quelltext-Seiten.

<?xml version="1.0" encoding="html"?>
<html><head><title>News Site</title></head><basefont face="Verdana"/><body><a href="?print" target="blank">Drucken?</a><p/><h2>Das ist ein Test</h2><p>
      Dies ist ein Text, der &uuml;berall erscheinen soll. Er kann auch 
      <a class="link" href="http://de.wikipedia.org">Hyperlinks</a> enthalten. 
      <a class="link" href="http://www.php.net">Weitere Informationen</a> &uuml;ber PHP finden sich im Internet
    </p><hr noshade="noshade"/>Peter Meier<hr noshade="noshade"/></body></html>

Drucken?

Das ist ein Test

Dies ist ein Text, der überall erscheinen soll. Er kann auch Hyperlinks enthalten. Weitere Informationen über PHP finden sich im Internet


Peter Meier

Wird nun auf den Link "Drucken?" geklickt, so übergibt der Webbrowser über die HTTP-Methode "GET" den Parameter "print". Damit das auch tatsächlich funktioniert, wird das Skript "dom_transform.php" in der Art erweitert, dass der GET-Parameter "print" ausgewertet wird und je nach gesetztem Wert ein anderes XSLT-Stylesheet lädt (hier mit dem Namen "phpnewsprint.xslt").

<?php
## Name     dom_transformpara.php

$dom    = new DomDocument();
## Laden der zu verarbeitenden XML-Datei.
$dom->load("data/source.xml");

$xsl    = new DomDocument();
## Auswertung des Parameters GET
if (isset($_GET['print']))
{
  ## Laden der Stylesheet-Datei mit den Umwandlungsanweisungen
  ## für die Druckansicht.
  $xsl->load("data/phpnewsprint.xslt");
}
else
{
  ## Laden der Stylesheet-Datei mit den Umwandlungsanweisungen.
  $xsl->load("data/phpnews.xslt");
}

## Erstellen eines XSLT-Prozessors, der die Umwandlung durchführt.
$xpr    = new XsltProcessor();
$xsl    = $xpr->importStylesheet($xsl);
$xpr->setParameter(null, "sendfile", $_SERVER['PHP_SELF']);

$output = $xpr->transformToDoc($dom);
echo $output->saveXML();
?>

Das XSLT-Skript "phpnewsprint.xslt" sieht dann wie folgt aus. Ein Klick auf den Link "Drucken?" zeigt die fertige Seite.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
  <xsl:template match="document">
    <html>
      <head>
        <title>News Site</title>
      </head>
      <basefont face="Verdana" />
      <body>
        <xsl:call-template name="title" />
        <xsl:apply-templates select="text" />
        <!-- Diesmal sollen keine (im Druck sinnlosen) "a"-Tags angezeigt
             werden, sondern Listen mit Quellangaben. -->
        <ul>
          <xsl:call-template name="linkliste" />
        </ul>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="*/link">
    <em><xsl:value-of select="." /> </em>
    [<xsl:value-of select="@source" />]
  </xsl:template> 

  <!-- Die Vorlage "linkliste" durchsucht das XML-Dokument nach allen
       enthaltenen Links und erzeugt für jede Fundstelle einen "li"-Tag
       mit dem passenden Text. -->
  <xsl:template name="linkliste">
    <xsl:for-each select="*/link">
      <li>[<xsl:value-of select="./@source" />]
        <xsl:value-of select="./text()" />
      <!-- Der Link selbst wird im Klartext ausgegeben. -->
      (<xsl:value-of select="./@href" />)</li>
    </xsl:for-each>
  </xsl:template>    

  <xsl:template match="text">
    <p><xsl:apply-templates /></p>
  </xsl:template>   
  
  <xsl:template name="title">
    <h2><xsl:value-of select="title" /></h2>
    <h3><xsl:value-of select="author" /></h3>
  </xsl:template>

  <xsl:template name="author">
    <hr noshade="noshade" />
    <xsl:value-of select="author" />
      <hr noshade="noshade" />
  </xsl:template>
</xsl:stylesheet>

Drucken?

Das ist ein Test

Dies ist ein Text, der überall erscheinen soll. Er kann auch Hyperlinks enthalten. Weitere Informationen über PHP finden sich im Internet


Peter Meier

Das ist ein Test

Peter Meier

Dies ist ein Text, der überall erscheinen soll. Er kann auch Hyperlinks [Q2] enthalten. Weitere Informationen [Q4] über PHP finden sich im Internet

  • [Q2] Hyperlinks (http://de.wikipedia.org)
  • [Q4] Weitere Informationen (http://www.php.net)

Nutzung von Parametern

Das Skript "dom_transformpara.php" nutzte einen Parameter, um das Verhalten des XSLT-Skripts aus dem PHP-Skript heraus zu steuern. In PHP wurde der Parameter wie folgt deklariert.

$xpr->setParameter(null, "sendfile", $_SERVER['PHP_SELF']);

Im XSLT-Skript "phpnewsprint.xslt" wurde der Parameter folgendermassen deklariert.

<xsl:param name="sendfile" />

Genutzt werden kann er dann wie eine Variable.

<xsl:value-of select="$sendfile" />

Der Vorteil dieser aufwendigen Aktion liegt in mehreren Effekten:

  • Weitere Ziele der Ausgabe - mit demselben Datenbestand - können leicht durch weitere XSLT-Skripts bedient werden. Hier kommen WAP-Handys, PDAs und dergleichen in Betracht.
  • Der Export der XML-Daten in ein Textformat oder andere XML-Strukturen ist schnell erledigt.
  • Der tatsächlich verwendete PHP-Code ist so gering, dass er leicht "perfekt" programmiert werden kann. Das Programm ist mit hoher Wahrscheinlichkeit fehlerfrei, da ein Grossteil der einfachen Aufarbeitungslogik im XML-Parser und im XSLT-Prozessor steckt.
  • PHP kann leicht gegen ASP, .NET, Java oder was auch immer ausgetauscht werden, ohne dass XML oder XSLT geändert werden muss.
  • Strenge Trennung der Gestaltung von den Daten.

XSLT-Funktionsübersicht

Die folgende Tabelle zeigt alle in PHP5 verfügbaren XSLT-Funktionen.

Funktion Beschreibung
importStylesheet Importiert ein XSLT-Skript zur Verwarbeitung.
transformToDoc Wandelt in ein Dokument vom Typ "DomDocument" um.
transformToUri Wandelt in eine Datei um und sendet sie zum angegebenen URI (zweiter Parameter). Der dadurch geöffnete Stream muss schreibbar sein, andernfalls schlägt die Methode fehl.
transformToXml Wandelt in eine Zeichenkette um.
setParameter Erstellt einen Parameter.
getParameter Ermittelt einen Parameter.
removeParameter Entfernt einen Parameter.
hasExlstSuppport Boolescher Wert, der ermittelt, ob Extended XSLT-Unterstützung vorliegt.
registerPHPFunctions Registriert eine PHP-Funktion im XSLT-Skript. Damit ist es möglich, aus XSLT heraus Funktionsaufrufe in das PHP-Modul zu starten - nichts für Puristen also.

XSLT mit PHP-Funktionen erweitern

Um XSLT mit PHP-Funktionen zu erweitern, wird ohne weiteren Parameter die XSLT-Funktion "registerPHPFunctions" aufgerufen. Danach stehen einfach alle in PHP bekannten Funktionen zur Verfügung, gleichgültig ob benutzerdefiniert oder fest eingebaut. Der Aufruf erfolgt beispielsweise innerhalb von "<xsl:value-of>" an den Stellen, wo XSLT- oder XPath-Funktionen gestartet werden können, allerdings nicht über den nicht standardkonformen Funktionsnamen "php:function()".

Das folgende Skript zeigt wie es geht. Es handelt sich um eine angepasste Fassung des bereits gezeigten Skripts.

<?php
## Name     dom_transfunction.php

## Funktion, welche die Aufrufadresse für das Skript ergibt. Hier
## könnten auch Datenbankabfragen starten.
function getBackLink()
{
  return $_SERVER['PHP_SELF'];
}

$dom = new DomDocument(); 
$dom->load("data/source.xml"); 

$xsl = new DomDocument(); 
if (isset($_GET['print']))
{
  $xsl->load("data/phpnewsprint.xslt");
}
else 
{
  $xsl->load("data/phpnewsfunction.xslt"); 
}

$xpr    = new XsltProcessor(); 
## Registrierung der PHP-Funktionen.
$xpr->registerPHPFunctions();
$xsl    = $xpr->importStylesheet($xsl); 
$output = $xpr->transformToDoc($dom); 
echo $output->saveXML(); 
?>

Auch das Umwandlungsskript muss angepasst werden. Der Parameter entfällt hier, dafür wird die Funktion aufgerufen. Damit ergeben sich trotz Nachteilen wie mangelhafter Kompatibilität ungeahnte Möglichkeiten.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Name     phpnewsfunction.xslt -->
<!-- Einbau des neuen Namensraumalias "php" in das Wurzelelement -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:php="http://php.net/xsl">
  <xsl:output encoding="html" />
  <xsl:template match="document">
    <html>
      <head>
        <title>News Site</title>
      </head>
      <basefont face="Verdana" />
      <body>
        <xsl:element name="a">
          <xsl:attribute name="href">
            <!-- Statt des Parameter "$sendfile" aus dem letzten Beispiel
                 erfolgt hier der Aufruf der PHP-Funktion. -->
            <xsl:value-of select="php:function('getBackLink')" />
            <xsl:text>?print</xsl:text>
          </xsl:attribute>
          <xsl:attribute name="target">blank</xsl:attribute>
          <xsl:text>Drucken?</xsl:text>
        </xsl:element>
        <p />
        <xsl:call-template name="title" />
        <xsl:apply-templates select="text" />
        <xsl:call-template name="author" />
      </body>
    </html>
  </xsl:template>

  <xsl:template match="text">
    <p><xsl:apply-templates /></p>
  </xsl:template>

  <xsl:template name="title">
    <h2><xsl:value-of select="title" /></h2>
  </xsl:template>

  <xsl:template match="*/link">
    <a class="link" href="{@href}">
      <xsl:value-of select="." />
    </a>
  </xsl:template>

  <xsl:template name="author">
    <hr noshade="noshade" />
    <xsl:value-of select="author" />
    <hr noshade="noshade" />
  </xsl:template>

</xsl:stylesheet>