/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.core.util.openxml; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Collection; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.apache.commons.io.IOUtils; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.openxml.OpenXMLDocument.HeaderReference; import org.olat.core.util.openxml.OpenXMLDocument.ListParagraph; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * * Initial date: 03.09.2013<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class OpenXMLDocumentWriter { private static final OLog log = Tracing.createLoggerFor(OpenXMLDocumentWriter.class); public static final String SCHEMA_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"; public static final String SCHEMA_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; public static final String SCHEMA_EXT_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; public static final String SCHEMA_DOC_PROPS_VT = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; public static final String SCHEMA_DC_TERMS = "http://purl.org/dc/terms/"; public static final String SCHEMA_DC = "http://purl.org/dc/elements/1.1/"; public static final String SCHEMA_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"; public static final String CT_RELATIONSHIP = "application/vnd.openxmlformats-package.relationships+xml"; public static final String CT_EXT_PROPERTIES = "application/vnd.openxmlformats-officedocument.extended-properties+xml"; public static final String CT_CORE_PROPERTIES = "application/vnd.openxmlformats-package.core-properties+xml"; public static final String CT_WORD_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"; public static final String CT_NUMBERING = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"; public static final String CT_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"; public static final String CT_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"; public static final String CT_THEME = "application/vnd.openxmlformats-officedocument.theme+xml"; public void createDocument(ZipOutputStream out, OpenXMLDocument document) throws IOException { //flush header... document.appendPageSettings(); //_rels out.putNextEntry(new ZipEntry("_rels/.rels")); createShadowDocumentRelationships(out); out.closeEntry(); //[Content_Types].xml out.putNextEntry(new ZipEntry("[Content_Types].xml")); createContentTypes(document, out); out.closeEntry(); //docProps/app.xml out.putNextEntry(new ZipEntry("docProps/app.xml")); createDocPropsApp(out); out.closeEntry(); //docProps/core.xml out.putNextEntry(new ZipEntry("docProps/core.xml")); createDocPropsCore(out); out.closeEntry(); //word/_rels/document.xml.rels out.putNextEntry(new ZipEntry("word/_rels/document.xml.rels")); createDocumentRelationships(out, document); out.closeEntry(); //word/media appendMedias(out, document); // word/theme/theme1.xml out.putNextEntry(new ZipEntry("word/theme/theme1.xml")); try(InputStream in = OpenXMLDocumentWriter.class.getResourceAsStream("_resources/theme1.xml")) { IOUtils.copy(in, out); } catch (IOException e) { log.error("", e); } out.closeEntry(); //word/numbering ZipEntry numberingDocument = new ZipEntry("word/numbering.xml"); out.putNextEntry(numberingDocument); appendNumbering(out, document); out.closeEntry(); //word/document.xml ZipEntry wordDocument = new ZipEntry("word/document.xml"); out.putNextEntry(wordDocument); OpenXMLUtils.writeTo(document.getDocument(), out, false); out.closeEntry(); //word/headerxxx.xml for(HeaderReference headerRef:document.getHeaders()) { ZipEntry headerDocument = new ZipEntry("word/" + headerRef.getFilename()); out.putNextEntry(headerDocument); IOUtils.write(headerRef.getHeader(), out, Charset.forName("UTF-8")); out.closeEntry(); } //word/styles.xml ZipEntry styles = new ZipEntry("word/styles.xml"); out.putNextEntry(styles); appendPredefinedStyles(out, document.getStyles()); out.closeEntry(); } protected void appendMedias(ZipOutputStream out, OpenXMLDocument document) throws IOException { for(DocReference img:document.getImages()) { try(FileInputStream in = new FileInputStream(img.getFile())) { ZipEntry wordDocument = new ZipEntry("word/media/" + img.getFilename()); out.putNextEntry(wordDocument); IOUtils.copy(in, out); OpenXMLUtils.writeTo(document.getDocument(), out, false); out.closeEntry(); } catch(Exception e) { log.error("", e); } } } private void appendNumbering(ZipOutputStream out, OpenXMLDocument document) { try(InputStream in = OpenXMLDocumentWriter.class.getResourceAsStream("_resources/numbering.xml")) { Collection<ListParagraph> numberingList = document.getNumbering(); if(numberingList != null && numberingList.size() > 0) { Document numberingDoc = OpenXMLUtils.createDocument(in); NodeList numberingElList = numberingDoc.getElementsByTagName("w:numbering"); Node numberingEl = numberingElList.item(0); for(ListParagraph numberingItem : numberingList) { Element abstractEl = document.createAbstractNumbering(numberingItem, numberingDoc); numberingEl.appendChild(abstractEl); Element numEl = document.createNumbering(numberingItem, numberingDoc); numberingEl.appendChild(numEl); } OpenXMLUtils.writeTo(numberingDoc, out, false); } else { IOUtils.copy(in, out); } } catch (IOException e) { log.error("", e); } } private void appendPredefinedStyles(ZipOutputStream out, OpenXMLStyles styles) { try(InputStream in = OpenXMLDocumentWriter.class.getResourceAsStream("_resources/styles.xml")) { if(styles != null) { Document stylesDoc = OpenXMLUtils.createDocument(in); NodeList stylesElList = stylesDoc.getElementsByTagName("w:styles"); if(stylesElList.getLength() == 1) { //Node stylesEl = stylesElList.item(0); //System.out.println("Append:" + stylesEl); } OpenXMLUtils.writeTo(stylesDoc, out, false); } else { IOUtils.copy(in, out); } } catch (IOException e) { log.error("", e); } } /* <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/> <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/> <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/> <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/> <Relationship Id="rId2" Type="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects" Target="stylesWithEffects.xml"/> <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml" /> </Relationships> */ protected void createDocumentRelationships(ZipOutputStream out, OpenXMLDocument document) { try { XMLStreamWriter writer = OpenXMLUtils.createStreamWriter(out); writer.writeStartDocument("UTF-8", "1.0"); writer.writeStartElement("Relationships"); writer.writeNamespace("", SCHEMA_RELATIONSHIPS); Document doc = OpenXMLUtils.createDocument(); Element relationshipsEl = (Element)doc.appendChild(doc.createElement("Relationships")); relationshipsEl.setAttribute("xmlns", SCHEMA_RELATIONSHIPS); addRelationship("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml", writer); addRelationship("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering", "numbering.xml", writer); if(document != null) { for(DocReference docRef:document.getImages()) { addRelationship(docRef.getId(), "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", "media/" + docRef.getFilename(), writer); } for(HeaderReference headerRef:document.getHeaders()) { addRelationship(headerRef.getId(), "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header", headerRef.getFilename(), writer); } } addRelationship(document.generateId(), "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "theme/theme1.xml", writer); writer.writeEndElement();// end Relationships writer.writeEndDocument(); writer.flush(); writer.close(); } catch (XMLStreamException e) { log.error("", e); } } /* <?xml version="1.0" encoding="UTF-8"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/> <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/> <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> </Relationships> */ protected void createShadowDocumentRelationships(ZipOutputStream zout) { try { XMLStreamWriter writer = OpenXMLUtils.createStreamWriter(zout); writer.writeStartDocument("UTF-8", "1.0"); writer.writeStartElement("Relationships"); writer.writeNamespace("", SCHEMA_RELATIONSHIPS); addRelationship("rId1", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml", writer); addRelationship("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml", writer); addRelationship("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "word/document.xml", writer); writer.writeEndElement();// end Relationships writer.writeEndDocument(); writer.flush(); writer.close(); } catch (XMLStreamException e) { log.error("", e); } } private final void addRelationship(String id, String type, String target, XMLStreamWriter writer) throws XMLStreamException { writer.writeStartElement("Relationship"); writer.writeAttribute("Id", id); writer.writeAttribute("Type", type); writer.writeAttribute("Target", target); writer.writeEndElement(); } /* <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:creator>docx4j</dc:creator> <cp:lastModifiedBy>docx4j</cp:lastModifiedBy> </cp:coreProperties> */ protected void createDocPropsCore(OutputStream out) { try { Document doc = OpenXMLUtils.createDocument(); Element propertiesEl = (Element)doc.appendChild(doc.createElement("cp:coreProperties")); propertiesEl.setAttribute("xmlns:cp", SCHEMA_CORE_PROPERTIES); propertiesEl.setAttribute("xmlns:dcterms", SCHEMA_DC_TERMS); propertiesEl.setAttribute("xmlns:dc", SCHEMA_DC); addDCProperty("creator", "OpenOLAT", propertiesEl, doc); addCPProperty("lastModifiedBy", "OpenOLAT", propertiesEl, doc); OpenXMLUtils.writeTo(doc, out, false); } catch (DOMException e) { log.error("", e); } } private final void addCPProperty(String name, String value, Element propertiesEl, Document doc) { Element defaultEl = (Element)propertiesEl.appendChild(doc.createElement("cp:" + name)); defaultEl.appendChild(doc.createTextNode(value)); } private final void addDCProperty(String name, String value, Element propertiesEl, Document doc) { Element defaultEl = (Element)propertiesEl.appendChild(doc.createElement("dc:" + name)); defaultEl.appendChild(doc.createTextNode(value)); } /* <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <properties:Properties xmlns:properties="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"> <properties:Application>OpenOLAT</properties:Application> <properties:AppVersion>9.1.0.</properties:AppVersion> </properties:Properties> */ protected void createDocPropsApp(OutputStream out) { try { Document doc = OpenXMLUtils.createDocument(); Element propertiesEl = (Element)doc.appendChild(doc.createElement("properties:Properties")); propertiesEl.setAttribute("xmlns:properties", SCHEMA_EXT_PROPERTIES); addExtProperty("Application", "Microsoft Macintosh Word", propertiesEl, doc); addExtProperty("AppVersion", "14.0000", propertiesEl, doc); OpenXMLUtils.writeTo(doc, out, false); } catch (DOMException e) { log.error("", e); } } private final void addExtProperty(String name, String value, Element propertiesEl, Document doc) { Element defaultEl = (Element)propertiesEl.appendChild(doc.createElement("properties:" + name)); defaultEl.appendChild(doc.createTextNode(value)); } /* <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" /> <Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml" /> <Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml" /> <Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml" /> <Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" PartName="/word/numbering.xml" /> <Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" PartName="/word/styles.xml" /> </Types> */ protected void createContentTypes(OpenXMLDocument document, ZipOutputStream out) { try { XMLStreamWriter writer = OpenXMLUtils.createStreamWriter(out); writer.writeStartDocument("UTF-8", "1.0"); writer.writeStartElement("Types"); writer.writeNamespace("", SCHEMA_CONTENT_TYPES); //Default createContentTypesDefault("rels", CT_RELATIONSHIP, writer); createContentTypesDefault("xml", "application/xml", writer); createContentTypesDefault("jpeg", "image/jpeg", writer); createContentTypesDefault("jpg", "image/jpeg", writer); createContentTypesDefault("png", "image/png", writer); createContentTypesDefault("gif", "image/gif", writer); //Override createContentTypesOverride("/docProps/app.xml", CT_EXT_PROPERTIES, writer); createContentTypesOverride("/docProps/core.xml", CT_CORE_PROPERTIES, writer); createContentTypesOverride("/word/document.xml", CT_WORD_DOCUMENT, writer); createContentTypesOverride("/word/styles.xml", CT_STYLES, writer); createContentTypesOverride("/word/numbering.xml", CT_NUMBERING, writer); createContentTypesOverride("/word/theme/theme1.xml", CT_THEME, writer); for(HeaderReference headerRef:document.getHeaders()) { createContentTypesOverride("/word/" + headerRef.getFilename(), CT_HEADER, writer); } writer.writeEndElement();// end Types writer.flush(); writer.close(); } catch (XMLStreamException e) { log.error("", e); } } private final void createContentTypesDefault(String extension, String type, XMLStreamWriter writer) throws XMLStreamException { writer.writeStartElement("Default"); writer.writeAttribute("Extension", extension); writer.writeAttribute("ContentType", type); writer.writeEndElement(); } private final void createContentTypesOverride(String partName, String type, XMLStreamWriter writer) throws XMLStreamException { writer.writeStartElement("Override"); writer.writeAttribute("PartName", partName); writer.writeAttribute("ContentType", type); writer.writeEndElement(); } }