# XML + XSLT to PDF mit FOP-Engine



## thommy.s (4. Jan 2011)

Hallo alle miteinander,

erst einmal allen noch ein gutes neues Jahr mit vielen Erfolgen!!!!

Mein Problem:

Ich habe aus einer Datenbank-Abfrage ein XML erstellt, welches ich mit FOP zu einem PDF-Ausdruck umwandeln will. Der Code der PDF-Erzeugungsklasse ist der folgende:


```
public class GeneratePDF {
	
	private static FopFactory fopFac = FopFactory.newInstance();	
	private static TransformerFactory transFac = TransformerFactory.newInstance();
	
	
	public static String generateIt(InputStream xml_in) {
		
		ByteArrayInputStream xmlin = (ByteArrayInputStream) xml_in;
		
		FOUserAgent fuo = fopFac.newFOUserAgent();
		
		//File xmlin = new File("out.xml");
		
		OutputStream out = null;
		
		File xslt = null;
		
		
		
		try {
			
			out = new FileOutputStream(new File ("output.pdf"));
			
			xslt = new File("layout.xsl");
			
			
			
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		try {
			
			
			Fop fop = fopFac.newFop(MimeConstants.MIME_PDF, fuo, out);
			
			Transformer transformer = transFac.newTransformer(new StreamSource(xslt));
									
			Source src = new StreamSource(xmlin);
			
			Result res = new SAXResult(fop.getDefaultHandler()); 
									
			transformer.transform(src, res);
			
			out.close();
							
			
		} catch (FOPException e1) {
			System.err.println("FOPException");
			e1.printStackTrace();
		} catch (TransformerConfigurationException e2) {
			System.err.println("TransformerConfigurationException");
			e2.printStackTrace();
		} catch (TransformerException e3) {
			System.err.println("TransformerException");
			e3.printStackTrace();
		} catch (IOException e) {
			
			e.printStackTrace();
		} 				
		
		return "true";				
	}
	
}
```

Der Quell XML-Strom sieht so aus (CompactFormat):

[XML]
<?xml version="1.0" encoding="UTF-8"?>

<root><datum>2010-12-06<taetigkeit kategorie="Sonstiges" auftraggeber="AIT" von="08:30:00" bis="09:30:00">bla..</taetigkeit><taetigkeit kategorie="Besprechung" auftraggeber="DEK" von="09:30:00" bis="11:40:00">bla..</taetigkeit><taetigkeit kategorie="Sonstiges" auftraggeber="AIT" von="12:15:00" bis="12:40:00">bla..</taetigkeit><taetigkeit kategorie="Entwicklung" auftraggeber="DEK" von="13:00:00" bis="16:30:00">bla..</taetigkeit></datum><datum>2010-12-07<taetigkeit kategorie="Dokumentation" auftraggeber="STDEK" von="08:30:00" bis="10:45:00">bla..</taetigkeit><taetigkeit kategorie="Besprechung" auftraggeber="STDEK" von="11:00:00" bis="11:50:00">bla..</taetigkeit></datum><datum>2010-12-08<taetigkeit kategorie="Entwicklung" auftraggeber="AIT" von="08:40:00" bis="09:30:00">bla..</taetigkeit><taetigkeit kategorie="Entwicklung" auftraggeber="DEK" von="10:00:00" bis="16:30:00">bla..</taetigkeit></datum><datum>2010-12-09<taetigkeit kategorie="Sonstiges" auftraggeber="DEK" von="08:40:00" bis="09:10:00">bla..</taetigkeit><taetigkeit kategorie="Entwicklung" auftraggeber="DEK" von="10:00:00" bis="17:00:00">bla..</taetigkeit></datum><datum>2010-12-10<taetigkeit kategorie="Entwicklung" auftraggeber="DEK" von="08:30:00" bis="11:00:00">bla..</taetigkeit><taetigkeit kategorie="Sonstiges" auftraggeber="AIT" von="12:00:00" bis="12:45:00">bla..</taetigkeit><taetigkeit kategorie="Besprechung" auftraggeber="AIT" von="12:45:00" bis="14:30:00">bla..</taetigkeit></datum></root>
[/XML]

Hab's aber selbstverständlich auch schon mit den anderen Formaten, die JDOM bietet, versucht. Tut mir leid, dass das jetzt alles auf einer Zeile angezeigt wird, ist aber IMHO unerheblich.

Die XSL-Datei, welche ich gern für die Formatierung verwenden würde, hat dieses Aussehen. Ist noch fast leer:

[XML]<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:fo="http://www.w3.org/1999/XSL/Format"
		 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<fo:root>

<fo:layout-master-set>
  <fo:simple-page-master master-name="A4">
    <!-- Page template goes here -->
  </fo:simple-page-master>
</fo:layout-master-set>

<foage-sequence master-name="A4">
  <!-- Page content goes here -->
  <xsl:template match="root" />
</foage-sequence>

</fo:root>
</xsl:stylesheet>[/XML]

Rufe ich die Methode transformer.transform (src, res) (sieher oben) auf, bekomme ich folgende Fehlermeldung:


```
System-ID unbekannt; Zeilennummer3; Spaltennummer1; Content is not allowed in trailing section.
```
.
 Es existieren nur 2 Zeilen (zumindest im CompactFormat). danach is nix mehr (lt. Texteditor).

Habe mir das XML-file dann einmal mit dem Vi als Hex-Editor angesehen, was folgendes Aussehen hat (die letzten beiden Zeilen):


```
00005d0: 2f74 6165 7469 676b 6569 743e 3c2f 6461  /taetigkeit></da
00005e0: 7475 6d3e 3c2f 726f 6f74 3e0d 0a         tum></root>..
```
Da befinden sich also zwei Punkte, die wohl durch das '0d' und '0a' repäsentiert sind, von denen eins sicher ein Zeilenumbruch ist.

Ist es möglich, dass diese daran schuld sind? Und falls ja, wie kann ich dem begegnen, lasse es mir ja per JDOM erzeugen?

Hab eine ähnlich Frage schon einmal in einem Thread hier gestellt, allerdings als Anschlussfrage, deshalb wohl auch keine Antwort bekommen. Vermute mal, da fehlte was. 

Falls hier noch etwas fehlt, nur zu. Ich liefere prompt nach.

Hoffe, mir kann jemand helfen.

LG Thomas


----------



## SlaterB (4. Jan 2011)

der Fehlermeldung nach wird doch eher der Anfang von Zeile 3 im XML ein Fehler gefunden, 
also vielleicht das Leerzeichen vor dem <root> in Zeile 2,

und das kommt weil die Zeile 4 der XSL-Datei aus einem Leerzeichen besteht, lösche dieses oder streiche Zeile 4 ganz


----------



## thommy.s (4. Jan 2011)

@SlaterB
Vielen Dank erst mal für die schnelle Antwort. Leider habe ich sie noch nicht ganz verstanden. Allerdings hast du mich auf einen Darstellungsfehler aufmerksam gemacht. In der Zeile 2 des XML-Stroms steht im Original kein Leerzeichen. hier noch einmal, so wie ich sie aus dem Texteditor rauskopiert habe


```
<?xml version="1.0" encoding="UTF-8"?>
<root><datum>2010-12-06<taetigkeit kategorie="Sonstiges" ..
```

Die Leerzeile dazwischen hatte die Forumseite dazugebastelt. Da habe ich nicht richtig geschaut. Was die Zeile 4 in der XSL-Datei angeht; ich habe sie entfernt, jedoch ohne Änderung. Vielleicht könntest du mich noch einmal aufklären, was du genau meinst...?

Grußt thomas


----------



## SlaterB (4. Jan 2011)

wenn es keinen Unterschied macht, dann war es ja nicht so wichtig,
ich dachte dass auf diesem Wege Leerzeichen an ungünstige Stellen kamen

aktuell fällt mir nichts ein, außer dass du vielleicht 
[c]</fo:root></xsl:stylesheet>[/c]
schreiben könntest um noch einen Zeilenumbruch dazwischen zu verhindern und ähnliches,
aber so genau sollte man das eigentlich nicht nehmen müssen,


hast du übrigens Standard-Beispiele aus Tutorials ausprobiert, gehen die ohne Fehlermeldung?


----------



## thommy.s (5. Jan 2011)

...ja, hatte ich. Ich lasse mir sowohl den Stream als auch ein File (XML) augeben. Bei dem File hatte ich die letzte Zeile einfach gelöscht und es als eingebequelle genutzt. Da bekam ich dann eine 'org.apache.fop.fo.ValidationException', was angesichts der noch leeren XSL-Datei auch richtig ist.

Auf deinen Hinweis hin habe ich es aber eben noch einmal gemacht, ohne irgendwas zu löschen. Das ging auch, so dass man nun wohl den Stream in den Fokus nehmen muss. Wie aber kann ich den denn aber überprüfen? Ausgegeben werden beide durch die gleiche Prozedur, einmal als Stream(ByteArrayOutputStream) und einmal als file. Ich möchte aber nicht mit Files arbeiten, es muss auch mit Streams gehen. 

Also ich erzeuge einen ByteArrayOutputStream, den ich in einem Zwischenschritt in einen ByteArrayInputStream umwandle. Wenn Code dazu gewünscht ist, liefere ich gern nach. Müsste ich dann von Arbeit aus machen...


----------



## thommy.s (5. Jan 2011)

Hier also, zunächst mal die Methode, die mir das XML erzeugt:


```
public OutputStream prepareData(int woche) {
		
		ByteArrayOutputStream bas = new ByteArrayOutputStream();
		
		
		
		Document doc = new Document();
		
								
		ResultSet rs_ready = getResultSet(woche);
		
		try {
			
			Element root = new Element("root");
			
			String akt_datum = "";
			
			Element elDatum = null;
			
			while (rs_ready.next()){			
				
				if (!(rs_ready.getDate("datum").toString().equals(akt_datum))) {
					
					akt_datum = rs_ready.getDate("datum").toString();
					
					elDatum = new Element ("datum");
					
					elDatum.addContent(akt_datum);		
					
					root.addContent(elDatum);
										
				}					
				
				
				Element elDoing = new Element("taetigkeit");
					
				elDoing.addContent(rs_ready.getString("aktivitaet"));
				elDoing.setAttribute("kategorie", rs_query.getString("kategorie"));
				elDoing.setAttribute("auftraggeber", rs_query.getString("auftraggeber"));
				elDoing.setAttribute("von", rs_query.getString("von"));
				elDoing.setAttribute("bis", rs_query.getString("bis"));
				
				elDatum.addContent(elDoing);
				
				
				
			}
			
			doc.setRootElement(root);
			
			try {
			
				XMLOutputter outputter = new XMLOutputter(Format.getCompactFormat());
				
				XMLOutputter outputter2 = new XMLOutputter(Format.getCompactFormat());
								
		    
				outputter2.output(doc,bas);
				
				outputter.output(doc, new FileOutputStream("out.xml", false));
				
				//outputter.output(doc,System.out);
				
				//System.out.println("Bis zum OutputStream geht's");
								
				
			} catch (IOException e) {
				
				e.printStackTrace();
				
				return null;
			}
			
		} catch (SQLException e) {
			
			e.printStackTrace();
			
			return null;
		}
		
		return bas;
	}
```


Und diese wird aufgerufen von folgender Methode, die das Ganze dann auch noch in einen ByteArrayInputStream umwandelt:


```
public InputStream createXMLStream(int woche) {
		
		Properties p = new Properties();
		
		ByteArrayOutputStream pos = (ByteArrayOutputStream) this.prepareData(woche);
		
		try {
			
			p.store(pos, "");
			
		} catch (IOException e) {
			
			e.printStackTrace();
			
			return null;
		}
		
		ByteArrayInputStream xmlin = new ByteArrayInputStream(pos.toByteArray());
		
		
		
		return xmlin;		
		
	}
```

Vielleicht kommen wir der Sache irgendwie näher... Kann es mir jedenfalls nicht recht erklären.


----------



## SlaterB (5. Jan 2011)

hab mir dein Beispiel angeschaut, etwas knapp,
in der gesamten Java-API gibt es keine Klasse mit einer Methode addContent(String), was verwendest du da?
XMLOutputter kommt mir allerdings bekannt vor und gibt es bei mir auch nicht, also gewiss irgendeine zusätzliche XML-Library,

ein mögliches Problem ist wirklich, dass vielleicht kein ordentliches XML erzeugt wird, byte-basierte Streams statt char-basierte Writer/ Reader könnten Ärger machen,
ich habe mal selber mit meinen Mitteln eine laufende Variante zusammengestellt, ist von deiner ziemlich entfernt, deshalb vielleicht nicht ganz hilfreich,
aber immerhin

XMLOutputter habe ich wie gesagt aktuell nicht, dann einen rustikalen einfachen XML-Writer:
XML Document Writer : WriterXMLJava

(edit: im Programm unten ist das XML-Beispiel eh nur <root></root>, das hätte man auch noch per Hand hinbekommen 
ob es für deine komplexeren Probleme ok ist, ist eine andere Frage, allerdings auch nur für org.w3c.dom geeignet,
ansonsten musst du wohl bei XMLOutputter bleiben..)


als Dokument org.w3c.dom, das kann man übrigens direkt als DOMSource verwenden, über ByteArrayStream gehts bei mir dann aber auch,

am XSL musste ich noch eine Menge feilen, damit ein richtiges Dummy-PDF erstellt wird:
[xml]
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:fo="http://www.w3.org/1999/XSL/Format"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">	
	<fo:root>

	<fo:layout-master-set>
		<fo:simple-page-master master-name="all" page-height="29.7cm"
			page-width="21cm" margin-top="0.6cm" margin-bottom="1cm"
			margin-left="2.4cm" margin-right="2.4cm">
			<fo:region-body margin-top="0.9cm" margin-bottom="3.3cm"/>
			<fo:region-after extent="3.3cm"/>
		</fo:simple-page-master>
	</fo:layout-master-set>

	<foage-sequence master-reference="all">
	  <fo:flow flow-name="xsl-region-body">
	  <fo:block>Test</fo:block>
	  </fo:flow>
	</foage-sequence>

	</fo:root>
</xsl:template>
</xsl:stylesheet>
[/xml]


```
public class Test {
    private static FopFactory fopFac = FopFactory.newInstance();
    private static TransformerFactory transFac = TransformerFactory.newInstance();

    public static String generateIt(InputStream xml_in)  throws Exception  {
        FOUserAgent fuo = fopFac.newFOUserAgent();

        OutputStream out = new FileOutputStream(new File("output.pdf"));
        File xslt = new File("layout.xsl");
        Fop fop = fopFac.newFop(MimeConstants.MIME_PDF, fuo, out);
        Transformer transformer = transFac.newTransformer(new StreamSource(xslt));
        Source src = new StreamSource(xml_in);
        Result res = new SAXResult(fop.getDefaultHandler());
        transformer.transform(src, res);
        out.close();
        return "true";
    }

    public static OutputStream prepareData()   throws Exception  {
        ByteArrayOutputStream bas = new ByteArrayOutputStream();
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element rootNode = doc.createElement("root");

        PrintWriter pw = new PrintWriter(System.out);
        XMLDocumentWriter writer = new XMLDocumentWriter(pw);
        writer.write(rootNode);
        pw.flush();
        
        PrintWriter pw2 = new PrintWriter(bas);
        XMLDocumentWriter writer2 = new XMLDocumentWriter(pw2);
        writer2.write(rootNode);
        pw2.flush();
        pw2.close();
        
        System.out.println("Bis zum OutputStream geht's");
        return bas;
    }

    public  static InputStream createXMLStream() throws Exception  {
        ByteArrayOutputStream pos = (ByteArrayOutputStream)prepareData();
        ByteArrayInputStream xmlin = new ByteArrayInputStream(pos.toByteArray());
        return xmlin;
    }

    public static void main(String[] args)  throws Exception  {
        InputStream in = createXMLStream();        
        generateIt(in);
    }
}
```

erzeugt ohne Fehlermeldung ein PDF mit Text 'Test'


----------



## SlaterB (5. Jan 2011)

meine Importe noch, kann grad nicht editieren:

```
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
```


----------



## thommy.s (5. Jan 2011)

Vielen Dank für deine große Mühe. Werde das nachher daheeme testen. 

Habe die JDOM-API benutzt, deshalb kanntest du vllt. einiges nicht. Das ist die API für den DAU, oder JDOM. 

Mit den anderen hatte ich mich bisher immer ein bißchen schwer getan... 

Halte dich auf dem Laufenden, ob ich es so adaptieren konnte.

Gruß Thomas


----------



## thommy.s (5. Jan 2011)

ok, hab's fast... Das Umschreiben war gloobe einfacher als ich dachte

Ein Problem hab' ich noch:

Den XMLDocumentWriter kann ich nicht auflösen. Kommt das aus einer externen Bibliothek, und wenn ja, aus welcher...?


ok, hab' grad erst gesehen, dass du einen Link dabei hattest für diesen:

Ich glaub', das ist wohl die antwort


----------



## SlaterB (5. Jan 2011)

------->


slaterb hat gesagt.:


> xmloutputter habe ich wie gesagt aktuell nicht, dann einen rustikalen einfachen xml-writer:
> xml document writer : Writerxmljava


<-----



edit: irgendwas hat alles in Kleinbuchstaben umgewandelt..


----------



## thommy.s (5. Jan 2011)

...ja, wie gesagt, wer lesen kann, ist klar im Vorteil. Und wer das dann auch noch tut...., nicht auszudenken.

Datt scheint geklappt zu haben, um das XSL-File kümmer' ich mich später...

Muss jetzt erst mal kurz n Schwatz abseits abhalten, aber Vielen Dank. Wenn man's dann noch direkt als DOMSource verwenden kann, wäre das ja schon der Hammer...

Hab' mir das mit den verschiedenen API's zwar mal theoretisch durchgelesen, aber mir war das alles zu wuselig. Da kam man mit JDOM irgendwie einfacher zum Ziel. aber diesmal wohl nicht....


----------



## thommy.s (9. Jan 2011)

@SlaterB

...so, bin erst heute wieder dazu gekommen, deshalb jetzt das Update.

Es scheint mit dem ByteArrayOutputStream nicht zu funktionieren, sobald es mehr als nur <root/> wird. Soll, heißen, ich hatte wieder die übliche Fehlermeldung.

Zum Glück erinnerte ich mich noch an deinen Tipp, dass man hier auch direkt DOMSource nutzen kann. So funktioniert es nun, im Übrigen mit deutlich weniger Code. Eine Frage noch, da ich da noch etwas verwirrt bin. Im Moment fhelt im Baum noch die Deklaration, also <?xml version="1.0" encoding="UTF-8"?>. Hab' aber irgendwie keine Methode gefunden, mit der ich das nachrüsten könnte... Fand da nur die Methoden (von org.w3c.dom.Document ausgehend) setXmlVersion() bzw. setStandAlone().

Wenn du mir da noch einmal einen Tipp geben könntest...?


----------



## SlaterB (10. Jan 2011)

brauchst du das für irgendwas? für XSL-Transformation doch wohl kaum, und ein direktes XML erstellst du ja nicht mehr,
ich vermute, dass das mit dem DOM-Document nichts zu tun hat, wenn ich nochmal den verlinkten XMLWriter anschaue:

```
/**
   * Output the specified DOM Node object, printing it using the specified
   * indentation string
   */
  public void write(Node node, String indent) {
    // The output depends on the type of the node
    switch (node.getNodeType()) {
    case Node.DOCUMENT_NODE: { // If its a Document node
      Document doc = (Document) node;
      out.println(indent + "<?xml version='1.0'?>"); // Output header
      [..]
```

mehr kann ich dazu nicht beitragen


----------



## thommy.s (12. Jan 2011)

...nein, eigentlich nicht. zumindest nicht in diesem Fall; ich dachte nur, das wäre sauberer (es könnte ja der Fall eintreten, dass man es doch braucht).

Danke dir jedenfalls, das hat mir weitergeholfen.


----------

