XSLT
Børre Stenseth

Basis

Hva

Vi ser litt på de grunnleggende genskapene til XSLT. Vi gjør det med noen enkle eksempler. Settingen er trolig ikke den mest typiske måten å bruke XSLT på, men den er enkel å eksperimentere med.

Vi stoler på nettleserens evne til å foreta transformasjoner "on-the-fly". Oppsettet i denne modulen er da at vi gir nettleseren adressen til en XML-fil. Den andre linja i XML-fila angir en transformasjon, en XSL-fil:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="booktrans.xslt"?>
<booklist>
    <book isbn="123456" pages="234">
    .....
    </book>
    <!-- more books -->
</booklist>

Vi trenger noe råmateriale å arbeide med. Gjennomgangseksempelet i denne modulen vil være en bokliste som er bygget opp slik:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE booklist SYSTEM "bokdok.dtd">
<?xml-stylesheet type="text/xsl"  href="boktrans.xslt"?>
<booklist>
    <book isbn="" pages="">
        <title/>
        <course/>
        <category/>
        <author/>
        <publisher/>
        <year/>
        <comment/>
    </book>
    <!-- more books -->
</booklist>

I de tre første linjene angir vi:

  1. at dette er en XML-fil og hvordan den er kodet
  2. at fila er av en bestemt type. Vi har laget en DTD-fil og kan validere den. Dette er strengt tatt ikke nødvendig for de transformasjonsøvelsene vi skal gjøre.
  3. at vi skal kople til en XSL-transformasjon. Navnet på XSL-fila (boktrans.xslt) vil endre seg etterhvert som vi skal gjøre ulike typer transformasjoner i det følgende.

Selve innholdet er en liste med bøker. Her kan du se et eksempel på en fil med litt innhold: bok1.xml.

For enkelhets skyld skal vi holde oss til transformasjoner fra XML til HTML. Du vil finne mange eksempler på andre typer transformasjoner i dette materialet, blandt annet i eksempelet Olympiade . Vi transformerer til HTML for at vi kan inspisere resultatet direkte i en nettleser som kan transformere automatisk. Sørg for at du bruker en slik nettleser når du arbeider med disse eksemplene. Alternativt må du bruke en av verktøyene som er beskrevet i modulen Verktøy, f.eks. XMLSpy, og transformere før du ser på resultatet i en hvilken som helst nettleser som kan tolke den HTML-koden din transformasjon leverer.

Enkel liste

Vi skriver en minimal transformasjon som bare skriver ut en liste over alle boktitlene, uten noen form for formattering. Transformasjonen bruker output-metode HTML og den lager XHTML 1.0 Strict. Vi lager XHTML strict i de første eksemplene for å demonstrere de grunnleggende mekanismene. Mot slutten av modulen ser vi på produksjon av HTML5.

<?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" 
    omit-xml-declaration="yes"
    indent="yes" 
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
    encoding="utf-8"/>
  <xsl:template match="/">
    <html>
      <head>
        <title>liste</title>
      </head>
      <body>
        <h1>List of books</h1>
        <xsl:for-each select="booklist/book">
          <p>
            <xsl:value-of select="title"/>
          </p>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Boklista transformert med denne transformasjonen direkte i en nettleser. Studer kildekoden og inspect element på denne siden:

bok2.xml https://borres.hiof.no/wep/xslt/basis/bok2.xml

Eller se det ferdigtransformerte resultatet direkte, studer kildekoden:

book_out2.html https://borres.hiof.no/wep/xslt/basis/book_out2.html

Vi merker oss følgende:

  • XSL-fila er en XML-fil. Det vil si at XSL er et språk i XML-familien.
  • Vi definerer et namespace for XSLT, med kortnavnet xsl. Vi kan bruker xsl som prefix når vi skal angi elementer som er XSL-elementer.
  • Vi angir hva slags output vi ønsker, og hvordan den skal kodes.
  • Vi angir en template som skal knyttes til det øverste nivået i den xml-fila vi skal transformere. / angir rota i treet. Vi kan ha flere templates.

Inne i templaten gjenkjenner vi HTML-kode. Denne teksten genereres slik som den er i resultatet av transformasjonen. Det eneste vi bruker fra den XML-fila vi jobber på finner vi ved kodesegmentet

    
<xsl:for-each select="booklist/book">
      <p>
        <xsl:value-of select="title"/>
      </p>
</xsl:for-each>
	

Vi befinner oss i en template som er koplet til rota i XML-strukturen og vi angir at vi vil ha en løkke over de nodene som heter book og som er barn av booklist. Når vi så har kommet inn i selve løkka, kan vi etterpå si at vi skal ha tak i verdien, innholdt, i elementet title Dette rører ved noe av det som er sentralt i XSLT. Vi ser at uttrykket boolist/book minner om måten vi skriver deler av en filpath på. I XSL-terminologi har vi oppgitt en XPath. Du vil trolig oppleve at det som skaper mest hodebry i XSL er å finne riktige slike path-uttrykk for å "peke på" elementer som er barn, søsken eller foreldre til andre elementer. Vi må hele tid finne den pathen i forhold til det stedet vi befinner oss, konteksten eller contekst node.

Her er det slik at vi endrer contekst i for-løkka og kan derfor angi title direkte, uten noen kvalifiserende path.

Templates

Vi tar for oss det samme problemet som ovenfor, å skrive ut en liste av boktitler. Denne gangen formulerer vi oss litt annerledes. Vi lager en egen template for bokutskrift. Vi får altså en template i tillegg:

<?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" 
     omit-xml-declaration="yes"
     indent="yes" 
     doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
     doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
     encoding="utf-8"/>
      
<xsl:template match="/">
<html>
<head>
   <title>liste</title>
</head>
<body>
<h1>Booklist</h1>
<xsl:for-each select="booklist/book">
   <xsl:apply-templates select="self::node()"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
 
<xsl:template match="book">
<p>
   <xsl:value-of select="title"/>
</p>
</xsl:template>
</xsl:stylesheet>

Boklista transformert med denne transformasjonen direkte i en nettleser. Studer kildekoden og inspect element på denne siden:

bok3.xml https://borres.hiof.no/wep/xslt/basis/bok3.xml

Eller se det fedig transformerte resultatet direkte:

book_out3.html https://borres.hiof.no/wep/xslt/basis/book_out3.html

Den nye templaten er laget for /booklist/book. Når vi går i løkka ber vi om å få anvendt templates som passer til den konteksten vi er i, self::node(). Vi kan erstatte self::node() med ., punktum, som kortform.

Se på alternativet nedenfor:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" 
     omit-xml-declaration="yes"
     indent="yes" 
     doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
     doctype-system= "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
     encoding="utf-8"/>
      
<xsl:template match="/">
<html>
<head>
   <title>minimum</title>
</head>
<body>
   <xsl:apply-templates select="booklist/book"/>
</body>
</html>
</xsl:template>
 
<xsl:template match="//book">
   <p>
   <xsl:value-of select="title"/>
   </p>
</xsl:template>
</xsl:stylesheet>

Vi har droppet for-løkka og bestilt en template for booklist/book. Vi får en automatisk gjennomgang av alle bøker. Vi kan lese select="booklist/book" slik: "hent alle book-noder under booklist".

Vi har dessuten generalisert den andre templaten slik at den matcher bok. Det vil si alle boknoder uansett hvem de er barn av. I dette tilfellet gjør det ingen forskjell sidan vår struktur bare har en type bokelement.

Litt stil

Vi ser av eksemplene ovenfor at vi kan generere HTML etter ønske, i hvert fall så langt. Den HTML-koden vi lager gir ikke noe spesielt pent resultat og HTML-sidene er av ubestemt art. Vi ønsker å kople til et stilsett. Vi gjør dette ved å endre transformasjonsfila slik at prologen i HTML-fila blir slik vi ønsker:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" 
     omit-xml-declaration="no"
     indent="yes" 
     doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
     doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
     encoding="utf-8"/>
 
  <xsl:template match="/">
    <html>
      <head>
        <!-- link to stylesheet -->
        <link rel="STYLESHEET" href="bokstil.css"/>
        <title>bokliste</title>
        <meta charset="UTF-8"/>
      </head>
      <body>
        <h1>Liste over alle bøker</h1>
        <ul>
          <xsl:apply-templates select="booklist/book"/>
        </ul>
      </body>
    </html>
  </xsl:template>
    
  <xsl:template match="book">
    <li class="enkel_liste">
      <xsl:value-of select="title"/>
    </li>
  </xsl:template>
</xsl:stylesheet>

Boklista transformert med denne transformasjonen direkte i en nettleser. Studer kildekoden og inspect element på denne siden:

bok4.xml https://borres.hiof.no/wep/xslt/basis/bok4.xml

Eller se det transformerte resultatet direkte:

book_out4.html https://borres.hiof.no/wep/xslt/basis/book_out4.html

Vi ser dessuten at vi har skrevet om slik at vi får en overskrift og en liste med en egen stil, enkel_liste.

Bedre liste

Vi beholder i hovedsak strukturen i den transformasjonen vi brukte over, men vi vil endre templaten for å skrive ut en bok slik at vi får ut mer informasjon om hver bok. Vi vil dessuten skrive ut mer informasjon i toppen av siden.

<?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" 
     omit-xml-declaration="yes"
     indent="yes" 
     doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
     doctype-system=
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
     encoding="utf-8"/>
 
<xsl:template match="/">
<html>
<head>
<!-- link to stylesheet -->
<link rel="STYLESHEET" href="bokstil.css" />
<meta charset="UTF-8"/>
<title>bokliste</title>
</head>
<body>
<h1>Liste over alle bøker</h1>
<xsl:apply-templates select="booklist/book"/>
</body>
</html>
</xsl:template>
 
<xsl:template match="book">
   <div class="booktitle">
    <xsl:value-of select="title"/>
   </div>
   <div><xsl:value-of select="author"/></div>
   <div><xsl:value-of select="publisher"/>, <xsl:value-of select="year"/></div>
   <div>Isbn: <xsl:value-of select="@isbn"/></div>
   <div class="kommentar">
    <xsl:value-of select="comment"/>
</div>
</xsl:template>
</xsl:stylesheet>

Boklista transformert med denne transformasjonen direkte i en nettleser. Studer kildekoden og inspect element på denne siden:

bok5.xml https://borres.hiof.no/wep/xslt/basis/bok5.xml

Eller se det transformerte resultatet direkte:

book_out5.html https://borres.hiof.no/wep/xslt/basis/book_out5.html

Utvalgt liste

Vi ønsker å endre transformasjonen slik at vi bare får ut bøker som har kategori XML. Foruten at vi endrer overskriften, så endrer vi løkka som går gjennom bøkene slik:

<xsl:apply-templates 
     select="booklist/book[category='XML']"/>

Boklista transformert med denne transformasjonen direkte i en nettleser:

bok6.xml https://borres.hiof.no/wep/xslt/basis/bok6.xml

Eller se det transformerte resultatet direkte:

book_out6.html https://borres.hiof.no/wep/xslt/basis/book_out6.html

Her ser vi at vi har et litt mer komplisert XPath-uttrykk. Vi har lagt til [category='XML'] som spesifiserer et utvalg basert på de aktuelle nodenes verdier.

HTML 5

Den nye headeren i HTML, <!DOCTYPE HTML>, er en utfordring når det gjelder å lage HTML5 dokumenter direkte i en transformasjon. Det er flere måter å løse dette på. Når vi skal lage en "on-the-fly" løsning i nettleseren er det best å benytte seg av en litt finurlig konstruksjon som er introdusert nettopp for å løse dette problemet på en sikker måte:
doctype-system="about:legacy-compat".

<?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" 
  doctype-system="about:legacy-compat"/>
  <xsl:template match="/">
    <html>
      <head>
        <title>liste</title>
        <meta charset="utf-8"/> 
      </head>
      <body>
        <h1>List of books</h1>
        <xsl:for-each select="booklist/book">
          <p>
            <xsl:value-of select="title"/>
          </p>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Vi gjentar det første enkle eksempelet overfor med denne transformasjonen

Boklista transformert med denne transformasjonen:

bok25.xml https://borres.hiof.no/wep/xslt/basis/bok25.xml

Eller se det transformerte resultatet direkte:

book_out25.html https://borres.hiof.no/wep/xslt/basis/book_out25.html

Alternativt

En alternativ måte å løse HTML5 problemet på er som følger:

<?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" omit-xml-declaration="yes" />
  
  <xsl:template match="/">
  <xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html></xsl:text>
    <html>
      <head>
        <title>liste</title>
        <meta charset="utf-8"/> 
      </head>
      <body>
        <h1>List of books</h1>
        <xsl:for-each select="booklist/book">
          <p>
            <xsl:value-of select="title"/>
          </p>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Boklista transformert med denne transformasjonen. Som du ser dra vi med oss HTML5-headeren ut på websiden. Sjekk kildekoden og inspiser elementer:

bok26.xml https://borres.hiof.no/wep/xslt/basis/bok26.xml

Boklista ferdig transformert med den samme transformasjonen:

book_out26.html https://borres.hiof.no/wep/xslt/basis/book_out26.html