All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.craigburke.document.builder.WordDocument.groovy Maven / Gradle / Ivy

package com.craigburke.document.builder

import groovy.xml.StreamingMarkupBuilder

import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

/**
 * Helper class for writing document in OOXML format
 * @author Craig Burke
 */
class WordDocument {

    private static final String ROOT_RELATIONSHIP_FILE = '_rels/.rels'
    private static final String CONTENT_FOLDER = 'word'
    private static final String IMAGE_FOLDER = 'media'

    private static final String XML_HEADER = ''
    private static final DOCUMENT_NAMESPACES = [
            w  : 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
            a  : 'http://schemas.openxmlformats.org/drawingml/2006/main',
            pic: 'http://schemas.openxmlformats.org/drawingml/2006/picture',
            wp : 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
            r  : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
    ]

    Map documentParts = [:]
    List contentTypes = []
    ZipOutputStream zipStream
    List contentTypeOverrides = []

    WordDocument(OutputStream out) {
        documentParts[DocumentPartType.ROOT.value] = new DocumentPart(type: DocumentPartType.ROOT)
        documentParts[DocumentPartType.DOCUMENT.value] = new DocumentPart(type: DocumentPartType.DOCUMENT)

        zipStream = new ZipOutputStream(out)
        addRelationship(
                "${CONTENT_FOLDER}/${DocumentPartType.DOCUMENT.fileName}",
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
                DocumentPartType.ROOT
        )

        contentTypes << new ContentType(
                extension: 'rels',
                type: 'application/vnd.openxmlformats-package.relationships+xml')
        contentTypes << new ContentType(extension: 'xml', type: 'application/xml')
        contentTypes << new ContentType(extension: 'png', type: 'image/png')
        contentTypes << new ContentType(extension: 'jpg', type: 'image/jpeg')
        contentTypes << new ContentType(extension: 'jpeg', type: 'image/jpeg')
    }

    String addRelationship(String target, String type, DocumentPartType part) {
        def currentRelationships = documentParts[part.value].relationships
        String id = "rId${currentRelationships.size() + 1}"
        currentRelationships << new Relationship(id: id, target: target, type: type)
        id
    }

    void write() {
        writeDocPropsFiles()
        writeRelationships()
        writeContentTypes()
        zipStream.close()
    }

    void writeDocPropsFiles() {
        zipStream.putNextEntry(new ZipEntry('docProps/app.xml'))
        zipStream << new StreamingMarkupBuilder().bind { builder ->
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << ['': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties']
            Properties {
                Application('Groovy Document Builder')
            }
        }
        zipStream.closeEntry()
        contentTypeOverrides << new ContentTypeOverride(
                partName: '/docProps/app.xml',
                contentType: 'application/vnd.openxmlformats-officedocument.extended-properties+xml'
        )

        zipStream.putNextEntry(new ZipEntry('docProps/core.xml'))
        zipStream << new StreamingMarkupBuilder().bind { builder ->
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << [
                    ''       : 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
                    'cp'     : 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
                    'dc'     : 'http://purl.org/dc/elements/1.1/',
                    'dcterms': 'http://purl.org/dc/terms/',
                    'xsi'    : 'http://www.w3.org/2001/XMLSchema-instance'
            ]
            coreProperties {
                dc.creator('Groovy Document Builder')
            }
        }
        zipStream.closeEntry()
        contentTypeOverrides << new ContentTypeOverride(
                partName: '/docProps/core.xml',
                contentType: 'conteapplication/vnd.openxmlformats-package.core-properties+xml'
        )
    }

    def generateDocument(Closure documentClosure) {
        zipStream.putNextEntry(new ZipEntry("${CONTENT_FOLDER}/${DocumentPartType.DOCUMENT.fileName}"))
        zipStream << new StreamingMarkupBuilder().bind { builder ->
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << DOCUMENT_NAMESPACES
            documentClosure.delegate = builder
            documentClosure(builder)

        }.toString()
        zipStream.closeEntry()
        addImageFiles()
    }

    String generateHeader(Closure headerClosure) {
        documentParts[DocumentPartType.HEADER.value] = new DocumentPart(type: DocumentPartType.HEADER)

        zipStream.putNextEntry(new ZipEntry("${CONTENT_FOLDER}/${DocumentPartType.HEADER.fileName}"))
        zipStream << new StreamingMarkupBuilder().bind { builder ->
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << DOCUMENT_NAMESPACES
            headerClosure.delegate = builder
            headerClosure(builder)
        }.toString()
        zipStream.closeEntry()

        addRelationship(
                DocumentPartType.HEADER.fileName,
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header',
                DocumentPartType.DOCUMENT
        )
    }

    String generateFooter(Closure footerClosure) {
        documentParts[DocumentPartType.FOOTER.value] = new DocumentPart(type: DocumentPartType.FOOTER)

        zipStream.putNextEntry(new ZipEntry("${CONTENT_FOLDER}/${DocumentPartType.FOOTER.fileName}"))
        zipStream << new StreamingMarkupBuilder().bind { builder ->
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << DOCUMENT_NAMESPACES
            footerClosure.delegate = builder
            footerClosure(builder)
        }.toString()
        zipStream.closeEntry()

        addRelationship(
                DocumentPartType.FOOTER.fileName,
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer',
                DocumentPartType.DOCUMENT
        )
    }

    private addImageFiles() {
        documentParts.each { String name, DocumentPart part ->
            part.images.each { image ->
                zipStream.putNextEntry(new ZipEntry("${CONTENT_FOLDER}/${IMAGE_FOLDER}/${image.name}"))
                zipStream << image.data
                zipStream.closeEntry()
            }
        }

    }

    String addImage(String name, byte[] imageData, DocumentPartType partType) {
        String id = addRelationship(
                "${IMAGE_FOLDER}/${name}",
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
                partType
        )
        documentParts[partType.value].images << [id: id, name: name, data: imageData]
        id
    }

    private void writeRelationships() {
        documentParts.each { String name, DocumentPart documentPart ->
            writeRelationshipsForPart(documentPart.type)
        }
    }

    private void writeRelationshipsForPart(DocumentPartType documentPart) {
        String fileLocation
        if (documentPart == DocumentPartType.ROOT) {
            fileLocation = ROOT_RELATIONSHIP_FILE
        } else {
            fileLocation = "${CONTENT_FOLDER}/_rels/${documentPart.fileName}.rels"
        }

        zipStream.putNextEntry(new ZipEntry(fileLocation))
        zipStream << new StreamingMarkupBuilder().bind {
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << ['': 'http://schemas.openxmlformats.org/package/2006/relationships']
            Relationships {
                documentParts[documentPart.value].relationships.each { Relationship relationship ->
                    Relationship(Id: relationship.id, Target: relationship.target, Type: relationship.type)
                }
            }
        }.toString()
        zipStream.closeEntry()
    }

    private void writeContentTypes() {
        zipStream.putNextEntry(new ZipEntry('[Content_Types].xml'))
        zipStream << new StreamingMarkupBuilder().bind {
            mkp.yieldUnescaped(XML_HEADER)
            namespaces << ['': 'http://schemas.openxmlformats.org/package/2006/content-types']
            Types {
                contentTypes.each { ContentType type ->
                    Default(Extension: type.extension, ContentType: type.type)
                }
                def nonRootParts = documentParts.findAll { it.key != DocumentPartType.ROOT.value }
                nonRootParts.each { String name, DocumentPart documentPart ->
                    Override(PartName: "/${CONTENT_FOLDER}/${documentPart.type.fileName}",
                            ContentType: documentPart.type.contentType)
                }
                contentTypeOverrides.each { ContentTypeOverride override ->
                    Override(PartName: override.partName, ContentType: override.contentType)
                }
            }
        }.toString()
        zipStream.closeEntry()
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy