XSL-FO
FOP
PDF
XSLT
Børre Stenseth

XSL-FO

Hva

XSL omfatter også et formateringsspråk (XSL-FO). Vi skal se litt på dette. Denne modulen er ikke noen komplett eller systematisk inføring i XSL-FO. Det er all grunn til å lese litteraturen og å bruke W3C.

XSL-FO splite en større rolle før CSS ble godt utbygd, med sidebeskrivelser osv. XSL-FO er mer omfattende når det gjelder detaljer og var vel ment å være det verktøyet som kan ivareta profesjonelle krav til dokumentformatering i forbindelse med trykking spesielt. Utviklingen går åpenbart i retning av at CSS tar inn i seg fler og fler av de mulighetene som tidligere var spesielle for XSL-FO.

Merk også at CSS og XSL_FO har en god del fellestrekk i måten å beskrive detaljerte blokk- eller paragraf formater på.

En viktig kilde for å forstå XS-FO og hensikten med det er prosjektet FOP [1] .

La oss ta utgangspunkt i følgende skisse av en to-stegs dokumenttransformasjon:

xslfo1
Transformasjon fra XML til XSL-FO og generering av PDF

Dette beskriver en normal arbeidssituasjon når vi arbeider med XSL-FO. Vi antar at vi har et dokument, doc, som er et XML-dokument av en eller annen type. Så gjør vi følgende:

  1. Vi transformerer doc.xml til doc.fo. Transformasjon, T1, er en vanlig XSLT-transformasjon. Resultatet, doc.fo, er et xml dokument som inneholder det vi måtte ønske fra opprinnelige dokumentet pluss formateringsinformasjon. Det er vanlig å bruke .fo som suffix på slike dokumenter.
  2. Vi transformerer doc.fo til det formatet vi måtte ønske. Typiske formater er, foruten pdf som her, er XML, RTF, tekst eller egentlig hva som helst. Transformasjonen T2 kan være ganske komplisert og krever selvsagt full kontroll over det formatet vi skal transformere til. Det vanlige er å bruke et av flere standard programmer til dette formålet. Det vanligste i øyeblikket ser ut til å være FOP [1] fra apache.org.

Det er selvsagt mulig å hoppe over den første transformasjonen og skrive doc.fo direkte for hånd. I det første eksempelet nedenfor gjør vi det. XMLSpy [2] er en egnet editor for å editere både fo-filer og xslt-filer. XMLSpy kan settes opp til å foreta FOP-transformasjoner dersom du har installert FOP på maskinen din.

Merk også at det finnes publiserte XML-formater som har sine egne ferdige T1-transformasjoner. Et eksempel som har vært med en stund er DocBook [3] som er XML-formater for bøker og artikler. Til DocBook er det utviklet flere transformasjoner som tar dokumenter fra .xml til .fo. Disse transformasjonene er parameterstyrte og muliggjør tilpassing på forskjellige nivåer. Det betyr altså at dersom vi lager dokumenter i DocBook format så kan vi bruke ferdige transformasjoner til både T1 og T2 jobbene på figuren ovenfor. Og vi kan få ut alle de formatene som den siste transformasjonen kan handtere. FOP greier eller planlegger blandt annet PDF, HTML, RTF, SVG, XML, PS, TXT. PDF er hovedformatet.

Et håndskrevet eksempel

Vi tar utgangspunkt i følgende enkle skjelett:

<?xml version="1.0" encoding="utf-8"?>
<root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <layout-master-set>
  </layout-master-set>
  <page-sequence>
  </page-sequence>
</root>

Vårt skjelett består av to deler. I den første delen, layout-master, setter vi opp de sidene vi vil bruke. I den andre delen, page-sequence, fyller vi sidene. Vi begynner veldig enkelt med å definere en sidemaster:

<fo:layout-master-set>   
<fo:simple-page-master 
   master-name="side"
   page-height="29.7cm"   page-width="21cm"
   margin-top="7cm"       margin-bottom="2cm"
   margin-left="2.5cm"    margin-right="2.5cm">
     <fo:region-body  margin-top="3cm" margin-bottom="1.5cm" />
     <fo:region-before extent="2cm" />
     <fo:region-after extent="1.5cm" />
   </fo:simple-page-master>
 </fo:layout-master-set>
 
xslfo2
XSL-FO sidestruktur

Først angir vi papirstørrelsen og så angir vi absolutte marger. Margene vill ikke kunne fylles med noe innhold. Det vi da sitter igjen med er den delen av arket vi kan disponere. Denne delen (page-reference-area) kan vi dele inn i 5 regioner. Margene er vist grå.

De fire regionenen som omgir region-body vil måtte plasseres forskjellig avhengig av hvilken skrivemåte vi har som referanse. For oss som skriver ovenfra og nedover, fra venstre mot høyre, vil de oppfattes som på tegningen.

Innhold kan rutes til de forskjellige regionene. Den løpende teksten vil typisk rutes til region-body, mens sidenummerering vil havne i region-after.

Vi legger den første av Shakespeares sonetter inn i page-sequence elementet i vår enkle fil og prøver oss med en .fo fil som i sin helhet ser slik ut:

<?xml version="1.0" encoding="ISO-8859-1"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
    <fo:simple-page-master master-name="side"
    page-height="29.7cm"
    page-width="21cm"
    margin-top="7cm"
    margin-bottom="2cm"
    margin-left="2.5cm"
    margin-right="2.5cm">
      <fo:region-body margin-top="3cm" 
                      margin-bottom="1.5cm"/>
      <fo:region-before extent="2cm"/>
      <fo:region-after extent="1.5cm"/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference="side">
    <fo:flow flow-name="xsl-region-body">
      <fo:block font-size="12pt" line-height="14pt" 
                font-family="Times" 
                white-space-collapse="false">
FROM fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou, contracted to thine own bright eyes,
Feed'st thy light's flame with self-substantial fuel,
Making a famine where abundance lies,
Thyself thy foe, to thy sweet self too cruel.
Thou that art now the world's fresh ornament
And only herald to the gaudy spring,
Within thine own bud buriest thy content
And, tender churl, mak'st waste in niggarding.
Pity the world, or else this glutton be,
To eat the world's due, by the grave and thee.
          </fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>

Vi ser at strukturen i page-sequence elementet er slik:


<fo:page-sequence master-reference="side">
<fo:flow flow-name="xsl-region-body">
    <fo:block>
    ...
    </fo:block>
  </fo:flow>
</fo:page-sequence>

Vi angir hvilken sidemaster vi skal bruke: "side", vi angir hvor innhold skal rutes: "xsl-region-body" og vi pakker innholdet i en (eller flere) block elementer. Det er et stort antall attributter som kan brukes i alle disse elementene for å kontrollere utseende og "flyt".

Resultatet av FOP-transformasjon av fila ovenfor til PDF blir slik: shake1.pdf. Ikke særlig imponerende eller nyttig, men du kan prøve å endre fila for å se effektene.

Vi tar eksempelet et steg til. Nå ønsker vi ta med to sonetter på hver sin side og vi vil ha en egen forside med et bilde. Vi vil dessuten nummerere de to sidene med sonetter på. File kan være slik:

<?xml version="1.0" encoding="ISO-8859-1"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
    <fo:simple-page-master master-name="forside"
    page-height="29.7cm"  page-width="21cm"  margin="2.5cm">
      <fo:region-body   margin="1.5cm"/>
      <fo:region-before extent="2cm"/>
      <fo:region-after extent="3cm"/>
    </fo:simple-page-master>
 <fo:simple-page-master master-name="side"
    page-height="29.7cm"  page-width="21cm"  margin="2.5cm">
      <fo:region-body   
          margin-left="3cm" 
          margin-top="3cm" 
          margin-bottom="1.5cm"/>
      <fo:region-before extent="2cm"/>
      <fo:region-after extent="1.5cm"/>
</fo:simple-page-master>
</fo:layout-master-set>
 <fo:page-sequence master-reference="forside">
    <fo:flow flow-name="xsl-region-body">
      <fo:block font-size="32pt" font-family="Times" 
                text-align="center">
        To utvalgte sonetter av Shakespeare
     </fo:block>
     <fo:block text-align="center" margin="20px">
        <fo:external-graphic src="theman.gif"/>
     </fo:block>
    </fo:flow>
  </fo:page-sequence>
  <fo:page-sequence master-reference="side">
     
     <fo:static-content flow-name="xsl-region-after">
      <fo:block font-size="12pt" font-family="Times" 
                text-align="center">
        side <fo:page-number/>
     </fo:block>
     </fo:static-content>
     
    <fo:flow flow-name="xsl-region-body">
      <fo:block font-size="12pt" font-family="Times"   
        white-space-collapse="false">
From fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou, contracted to thine own bright eyes,
Feed'st thy light's flame with self-substantial fuel,
Making a famine where abundance lies,
Thyself thy foe, to thy sweet self too cruel.
Thou that art now the world's fresh ornament
And only herald to the gaudy spring,
Within thine own bud buriest thy content
And, tender churl, mak'st waste in niggarding.
Pity the world, or else this glutton be,
To eat the world's due, by the grave and thee.
     </fo:block>
        
    <fo:block font-size="12pt" font-family="Times" 
      white-space-collapse="false" break-before="page">
When forty winters shall besiege thy brow,
And dig deep trenches in thy beauty's field,
Thy youth's proud livery, so gaz'd on now,
Will be a tatter'd weed, of small worth held:
Then being ask'd where all thy beauty lies,
Where all the treasure of thy lusty days,
To say, within thine own deep-sunken eyes,
Were an all-eating shame and thriftless praise.
How much more praise deserv'd thy beauty's use,
If thou couldst answer, 'This fair child of mine
Shall sum my count, and make my old excuse,'
Proving his beauty by succession thine!
This were to be new made when thou art old,
And see thy blood warm when thou feel'st it
cold.
      </fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>
William

Som du ser har vi introdusert en ny sidemaster som heter forside. De to block-elementene som legger ut sonetter er helt like bortsett fra at den andre bestiller et sideskift. Vi oppdager fort at det er lite effektivt å skrive fo-filer for hånd. I praksis er dette en ganske masete øvelse som krever mye detaljarbeid og mye gjentagelse av detaljer. fo-filene blir ofte ganske omfattende.

Resultatet av FOP-transformasjon av fila ovenfor til PDF blir slik: shake2.pdf. Transformasjonen forutsetter et bilde, theman.gif, i samme katalog som fo-fila.

Eksempel 2

Vi tar fatt i resultatene fra alle sprint øvelsen i de siste olympiadene og vi ønsker å lage en PDF-utskrift sortert på olympiade, øvelse og løperens tid. Oppsettet blir slik:

xslfo3
Transformasjon av olympiske data

all_results.xml er en XML-fil som beskriver resultatene. Starten av fila ser slik ut:

<?xml version="1.0" encoding="utf-8"?>
<IOC>
  <OlympicGame place="Sidney" year="2000">
    <event dist="100m">
      <athlet>
        <name>Maurice Greene</name>
        <nation>USA</nation>
        <result>9.87</result>
      </athlet>
      <athlet>
        <name>Obadele Thompson</name>
        <nation>BAR</nation>
        <result>10.04</result>
      </athlet>
      ...

xml-to-fo.xslt er en XSLT-fil. Siden vi har definert våre olympiske resultater i vårt eget format må vi skrive denne selv:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.1"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <xsl:output method="xml" version="1.0"
              encoding="utf-8" indent="yes"/>
<xsl:template match="/">
  <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
  <!-- layout for the first page -->
  <fo:simple-page-master master-name="coverpage"
                page-height="29.7cm"
                page-width="21cm"
                margin-top="7cm"
                margin-bottom="2cm"
                margin-left="2.5cm"
                margin-right="2.5cm">
    <fo:region-body margin-top="3cm"  margin-bottom="1.5cm"/>
    <fo:region-before extent="2cm"/>
    <fo:region-after extent="1.5cm"/>
  </fo:simple-page-master>
  <!-- layout for all other pages -->
  <fo:simple-page-master master-name="pages"
                page-height="29.7cm"
                page-width="21cm"
                margin-top="1cm"
                margin-bottom="2cm"
                margin-left="2.5cm"
                margin-right="2.5cm">
    <fo:region-body margin-top="1cm"  margin-bottom="1cm"/>
    <fo:region-before extent="2cm"/>
    <fo:region-after extent="1.5cm"/>
  </fo:simple-page-master>
  </fo:layout-master-set>
  <!-- filling the front page -->
  <fo:page-sequence master-reference="coverpage">
  <fo:flow flow-name="xsl-region-body">
  <fo:block font-weight="bold" font-size="28pt"
            line-height="38pt" font-family="Times">
 Olympiske resultater
  </fo:block>
  <fo:block font-weight="normal" font-size="13pt"
            line-height="15pt" font-family="Times">
  Sprintøvelsene i de siste olympiadene
  </fo:block>
    </fo:flow>
    </fo:page-sequence>
  <!-- doing all olympics in turn -->
  <xsl:apply-templates select="/IOC/OlympicGame">
    <xsl:sort select="@year"/>
  </xsl:apply-templates>
</fo:root>
</xsl:template>
<xsl:template match="//OlympicGame">
    <fo:page-sequence  master-reference="pages">
    <fo:flow flow-name="xsl-region-body">
      <fo:block font-weight="bold" font-size="18pt"
                line-height="28pt" font-family="Times"
                padding-top="0cm"
                border-bottom-color="black"
                border-bottom-style="solid">
        <xsl:element name="fo:external-graphic">
           <xsl:attribute name="src">
           <xsl:value-of select="@place"/>.gif
           </xsl:attribute>
        </xsl:element>
      </fo:block>
              <xsl:apply-templates select="event">
              <xsl:sort select="@dist"/>
              </xsl:apply-templates>
      </fo:flow>
    </fo:page-sequence>
</xsl:template>
<xsl:template match="//event">
  <fo:block font-weight="bold" font-size="12pt"
           line-height="14pt" font-family="Times"
            padding-top="1cm"  >
         <xsl:value-of select="@dist"/>
  </fo:block>
  <xsl:apply-templates select="athlet">
  <xsl:sort  data-type="number" select="result"/>
  </xsl:apply-templates>
</xsl:template>
<xsl:template match="//athlet">
  <fo:block font-size="10pt" line-height="14pt"
           font-family="Times" >
      <xsl:value-of select="name"/>,
      <xsl:value-of select="nation"/>   :
      <xsl:value-of select="result"/>
  </fo:block>
</xsl:template>
</xsl:stylesheet>

Vi ser at transformasjonen består av to hovedkomponenter. En del som setter opp layout. Så ser vi at de ulike outputdelene, faste tekster og verdier hentet fra XML-fila pakkes inn i blokker med format-attributter.

olympic.fo som er resultatet av den første transformasjonen er en ganske omfattende og tunglest fil. Du kan se denne i et eget vindu: olympic.fo

FOP-transformasjonen fram til det endelige PDF-formatet gjør vi (på MS-Windows)ved å kjøre en bat-fil som tar olympic.fo som input og som produserer olympic.pdf som output. Installasjon og oppsett av FOP er ganske rett fram og greitt dokumentert, se referanser.

olympic.pdf kan du se her: olympic.pdf

Referanser
  1. FOP apache.org xmlgraphics.apache.org/fop/ 24-02-2014
  1. XML Spy Altova www.xmlspy.com 01-03-2014
  1. DocBook DocBook.org www.docbook.org/ 24-02-2014