Word-Dateien aus Templates mit FreeMarker in Grails erstellen
In einem aktuellen Kundenprojekt sollen dynamisch erzeugte Word-Dokumente zum Download angeboten werden. Dabei sollen die Word-Dokumente genau wie die Vorlage des Kunden aussehen und an den entsprechenden Stellen mit Werten aus der Applikation gefüllt sein.
In diesem konkreten Fall erzeugt die Applikation aus einer Fülle an Daten ein Übersichtsdokument in Word - ganz nach dem Template des Kunden.
Dieser Blog-Post soll zeigen, wie einfach die Erstellung eines Word-Dokuments - auch unter Verwendung einer Vorlage - mittlerweile ist.
Word und Office Open XML
Das "neue" Word-Format "docx" ist im Prinzip eine zip-Datei, die verschiedene Ordner und Dateien enthält [1]. Jede docx-Datei kann entpackt, manuell verändert und wieder gepackt werden. Dabei findet sich der Inhalt des Dokuments maßgeblich in der XML-Datei "word/document.xml" wieder.
FreeMarker Templates
- Word-Datei als docx speichern und mit einem Zip-Porgramm entpacken
- Die Datei "word/document.xml" in einem Texteditor öffnen und FreeMarker-Variablen in Form von ${titel}, ${datum} etc. an den gewünschten Stellen einfügen
- Die in Schritt 1 entstandene Ordnerstruktur wieder zu einem docx-Dokument zippen.
Implementierung in Groovy / Grails
Die Schritte, um jedes beliebige Template als Word-Datei anbieten zu können, sind sehr einfach. Der Ablauf ist wie folgt:
docx-Template vom ClassPath in ein temporäres Verzeichnis kopieren und entzippen
// Specify path to template in classpath
def applicationContext = grailsApplication.mainContext
def basePath = applicationContext.getResource("/").getFile().toString()
def source = new File("${basePath}/../src/groovy/com/exensio/templates/Project_Summary.docx")
// Specify destination file name and path (uses temp directory)
def destinationFile = File.createTempFile("Project_Summary", ".zip")
def destinationFolderName = FilenameUtils.getBaseName(destinationFile.name)
def destinationFolder = new File(destinationFile.getParent() + "/" + destinationFolderName)
destinationFolder.mkdir()
// Copy the Word template to a temp file and unzip it to the temp folder
FileUtils.copyFile(source, destinationFile)
def ant = new AntBuilder();
ant.unzip(src: destinationFile, dest:destinationFolder, overwrite:"true")
XML-Datei mit FreeMarker verarbeiten
// Specify FreeMarker to load from temp directory
def freeMarkerConfig = new Configuration()
freeMarkerConfig.setDefaultEncoding("utf-8")
def fileTemplateLoader = new FileTemplateLoader(new File(System.getProperty("java.io.tmpdir")))
freeMarkerConfig.setTemplateLoader(fileTemplateLoader);
// Load the template (destinationFolderName has to be relative to the temp directory!)
def template = freeMarkerConfig.getTemplate(destinationFolderName + "/word/document.xml")
// Process template and write it to itself
def writer = new FileWriter(new File(destinationFolder.getAbsolutePath() + "/word/document.xml")
template.process(model, writer);
Word-Dokument als docx zippen und zum Download anbieten
// Zip the docx folder to the final docx file
ant.zip(destfile: destinationFile, basedir:destinationFolder)
// Send file as stream to response output stream
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
response.setHeader("Content-Disposition", "attachment; filename=\"Project_Summary.docx\"");
def inputStream = new FileInputStream(destinationFile)
IOUtils.copy(inputStream, response.outputStream)
Streams schließen und temporäre Dateien/Ordner aufräumen
finally {
IOUtils.closeQuietly(response.outputStream)
IOUtils.closeQuietly(inputStream)
FileUtils.deleteQuietly(destinationFile)
FileUtils.deleteQuietly(destinationFolder)
}