package com.github.miachm.sods; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import java.io.*; import java.util.HashMap; import java.util.Map; /** * Internal class for generate ODS files. */ class OdsWritter { private final static String office = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; private final static String table_namespace = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; private final static String text_namespace = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; private final static String font_namespace = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"; private final static String style_namespace = "urn:oasis:names:tc:opendocument:xmlns:style:1.0"; private final static String metadata_namespace = "http://purl.org/dc/elements/1.1/"; private SpreadSheet spread; private Compressor out; private Map<Style, String> stylesUsed = new HashMap<>(); private Map<ColumnStyle, String> columnStyleStringMap = new HashMap<>(); private Map<RowStyle, String> rowStyleStringMap = new HashMap<>(); private Map<TableStyle, String> tableStyleStringMap = new HashMap<>(); private final String MIMETYPE= "application/vnd.oasis.opendocument.spreadsheet"; private OdsWritter(OutputStream o, SpreadSheet spread) { this.spread = spread; this.out = new Compressor(o); } public static void save(OutputStream out,SpreadSheet spread) throws IOException { new OdsWritter(out,spread).save(); } private void save() throws IOException { writeManifest(); writeMymeType(); try { writeSpreadsheet(); } catch (XMLStreamException e) { throw new GenerateOdsException(e); } out.close(); } private void writeManifest() { try { ByteArrayOutputStream output = new ByteArrayOutputStream(1024); XMLStreamWriter out = XMLOutputFactory.newInstance().createXMLStreamWriter( new OutputStreamWriter(output, "utf-8")); final String namespace = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"; out.writeStartDocument("UTF-8", "1.0"); out.writeStartElement("manifest:manifest"); out.writeAttribute("xmlns:manifest", namespace); out.writeAttribute("manifest:version", "1.2"); out.writeStartElement("manifest:file-entry"); out.writeAttribute("manifest:full-path", "/"); out.writeAttribute("manifest:version", "1.2"); out.writeAttribute("manifest:media-type", MIMETYPE); out.writeEndElement(); out.writeStartElement("manifest:file-entry"); out.writeAttribute("manifest:full-path", "content.xml"); out.writeAttribute("manifest:media-type", "text/xml"); out.writeEndElement(); out.writeEndElement(); out.writeEndDocument(); out.close(); byte[] bytes = output.toByteArray(); this.out.addEntry(bytes, "META-INF/manifest.xml"); } catch (XMLStreamException | IOException pce) { throw new GenerateOdsException(pce); } } private void writeMymeType() throws IOException { out.addEntry(MIMETYPE.getBytes(),"mimetype"); } private void writeSpreadsheet() throws UnsupportedEncodingException, XMLStreamException { ByteArrayOutputStream output = new ByteArrayOutputStream(1024); XMLStreamWriter out = XMLOutputFactory.newInstance().createXMLStreamWriter( new OutputStreamWriter(output, "utf-8")); out.writeStartDocument("UTF-8", "1.0"); out.writeStartElement( "office:document-content"); out.writeAttribute("xmlns:office", office); out.writeAttribute("xmlns:table", table_namespace); out.writeAttribute("xmlns:text",text_namespace); out.writeAttribute("xmlns:fo",font_namespace); out.writeAttribute("xmlns:style", style_namespace); out.writeAttribute("xmlns:dc", metadata_namespace); out.writeAttribute("office:version", "1.2"); writeStyles(out); writeContent(out); out.writeEndElement(); out.writeEndDocument(); out.close(); try { this.out.addEntry(output.toByteArray(),"content.xml"); } catch (IOException e) { throw new GenerateOdsException(e); } } private void writeContent(XMLStreamWriter out) throws XMLStreamException { out.writeStartElement("office:body"); out.writeStartElement("office:spreadsheet"); for (Sheet sheet : spread.getSheets()) { out.writeStartElement("table:table"); out.writeAttribute("table:name", sheet.getName()); if (sheet.isHidden()) { TableStyle tableStyle = new TableStyle(); tableStyle.setHidden(true); String name = tableStyleStringMap.get(tableStyle); if (name != null) out.writeAttribute("table:style-name", name); } writeColumnsStyles(out, sheet); writeContent(out, sheet); out.writeEndElement(); } out.writeEndElement(); out.writeEndElement(); } private void writeColumnsStyles(XMLStreamWriter out, Sheet sheet) throws XMLStreamException { for (int i = 0;i < sheet.getMaxColumns();i++){ out.writeStartElement("table:table-column"); Double width = sheet.getColumnWidth(i); if (width != null) { ColumnStyle columnStyle = new ColumnStyle(); columnStyle.setWidth(width); String name = columnStyleStringMap.get(columnStyle); if (name != null) out.writeAttribute("table:style-name", name); } if (sheet.columnIsHidden(i)) out.writeAttribute("table:visibility", "collapse"); out.writeEndElement(); } } private void writeContent(XMLStreamWriter out, Sheet sheet) throws XMLStreamException { for (int i = 0;i < sheet.getMaxRows();i++) { out.writeStartElement("table:table-row"); writeRowStyles(out, sheet, i); for (int j = 0; j < sheet.getMaxColumns();j++) { Range cell = sheet.getRange(i, j); writeCell(out, cell); } out.writeEndElement(); } } private void writeRowStyles(XMLStreamWriter out, Sheet sheet, int i) throws XMLStreamException { if (sheet.rowIsHidden(i)) out.writeAttribute("table:visibility", "collapse"); writeRowHeight(out, sheet, i); } private void writeCell(XMLStreamWriter out, Range range) throws XMLStreamException { String formula = range.getFormula(); Style style = range.getStyle(); Range mergedCells[] = range.getMergedCells(); if (mergedCells.length > 0) { if (mergedCells[0].getColumn() != range.getColumn() || mergedCells[0].getRow() != range.getRow()) { out.writeStartElement("table:covered-table-cell"); out.writeEndElement(); return; } } out.writeStartElement("table:table-cell"); if (mergedCells.length > 0) { if (mergedCells[0].getNumColumns() > 1) out.writeAttribute("table:number-columns-spanned", "" + mergedCells[0].getNumColumns()); if (mergedCells[0].getNumRows() > 1) out.writeAttribute("table:number-rows-spanned", "" + mergedCells[0].getNumRows()); } if (formula != null) out.writeAttribute("table:formula", formula); setCellStyle(out, style); writeValue(out, range); out.writeEndElement(); } private void setCellStyle(XMLStreamWriter out, Style style) throws XMLStreamException { if (!style.isDefault()) { String key = stylesUsed.get(style); if (key == null) { key = "cel" + stylesUsed.size(); stylesUsed.put(style, key); } out.writeAttribute("table:style-name", key); } } private void writeValue(XMLStreamWriter out, Range range) throws XMLStreamException { Object v = range.getValue(); if (v != null) { OfficeValueType valueType = OfficeValueType.ofJavaType(v.getClass()); valueType.write(v, out); out.writeStartElement("text:p"); String text = v.toString(); for (int i = 0; i < text.length(); i++) { if (text.charAt(i) == ' ') { out.writeStartElement("text:s"); int cnt = 0; while (i+cnt < text.length() && text.charAt(i + cnt) == ' ') { cnt++; } if (cnt > 1) out.writeAttribute("text:c", "" + cnt); i += cnt - 1 ; out.writeEndElement(); } else if (text.charAt(i) == '\t') { out.writeEmptyElement("text:tab"); } else if (text.charAt(i) == '\n') { out.writeEndElement(); out.writeStartElement("text:p"); } else out.writeCharacters("" + text.charAt(i)); } out.writeEndElement(); } OfficeAnnotation annotation = range.getAnnotation(); if (annotation != null) { out.writeStartElement("office:annotation"); if (annotation.getLastModified() != null) { out.writeStartElement("dc:date"); out.writeCharacters(annotation.getLastModified().toString()); out.writeEndElement(); } if (annotation.getMsg() != null) { out.writeStartElement("text:p"); out.writeCharacters(annotation.getMsg()); out.writeEndElement(); } out.writeEndElement(); } } private void writeRowHeight(XMLStreamWriter out, Sheet sheet, int i) throws XMLStreamException { Double height = sheet.getRowHeight(i); if (height != null) { RowStyle rowStyle = new RowStyle(); rowStyle.setHeight(height); String name = rowStyleStringMap.get(rowStyle); if (name != null) out.writeAttribute("table:style-name", name); } } private void writeStyles(XMLStreamWriter out) throws XMLStreamException { out.writeStartElement("office:automatic-styles"); for (Sheet sheet : spread.getSheets()) { for (int i = 0; i < sheet.getMaxRows(); i++) { for (int j = 0; j < sheet.getMaxColumns(); j++) { Range range = sheet.getRange(i,j); Style style = range.getStyle(); if (!style.isDefault()) { writeCellStyle(out, style); } Double width = sheet.getColumnWidth(j); if (width != null) { writeColumnStyle(out, width); } } Double height = sheet.getRowHeight(i); if (height != null) { writeRowStyle(out, height); } } if (sheet.isHidden()) { writeTableStyle(out, sheet); } } out.writeEndElement(); } private void writeCellStyle(XMLStreamWriter out, Style style) throws XMLStreamException { String key = stylesUsed.get(style); if (key == null) { key = "cel" + stylesUsed.size(); out.writeStartElement("style:style"); out.writeAttribute("style:family", "table-cell"); out.writeAttribute("style:name", key); if (style.hasTableCellProperties()) { out.writeStartElement("style:table-cell-properties"); if (style.getBackgroundColor() != null) { out.writeAttribute("fo:background-color", style.getBackgroundColor().toString()); } if (style.isWrap()) { out.writeAttribute("fo:wrap-option", "wrap"); } if(style.hasBorders()) { writeBorderStyle(out, style); } out.writeEndElement(); } out.writeStartElement("style:text-properties"); if (style.isItalic()) out.writeAttribute("fo:font-style", "italic"); if (style.isBold()) out.writeAttribute("fo:font-weight", "bold"); if (style.isUnderline()) { out.writeAttribute("style:text-underline-style", "solid"); out.writeAttribute("style:text-underline-type", "single"); out.writeAttribute("style:text-underline-width", "auto"); out.writeAttribute("style:text-underline-color", "font-color"); } if (style.getFontSize() != -1) out.writeAttribute("fo:font-size", "" + style.getFontSize() + "pt"); if (style.getFontColor() != null) out.writeAttribute("fo:color", style.getFontColor().toString()); out.writeEndElement(); if(style.getTextAligment() != null) { out.writeStartElement("style:paragraph-properties"); out.writeAttribute("fo:text-align", toValue(style.getTextAligment())); out.writeEndElement(); } out.writeEndElement(); stylesUsed.put(style, key); } } private String toValue(Style.TEXT_ALIGMENT textAligment) { switch (textAligment) { case Left: return "start"; case Center: return "center"; case Right: return "end"; } return null; } private void writeColumnStyle(XMLStreamWriter out, Double width) throws XMLStreamException { ColumnStyle columnStyle = new ColumnStyle(); columnStyle.setWidth(width); if (!columnStyleStringMap.containsKey(columnStyle)) { String key = "co" + columnStyleStringMap.size(); out.writeStartElement("style:style"); out.writeAttribute("style:family", "table-column"); out.writeAttribute("style:name", key); out.writeStartElement("style:table-column-properties"); out.writeAttribute("style:column-width", width.toString() + "mm"); out.writeEndElement(); out.writeEndElement(); columnStyleStringMap.put(columnStyle, key); } } private void writeRowStyle(XMLStreamWriter out, Double height) throws XMLStreamException { RowStyle rowStyle = new RowStyle(); rowStyle.setHeight(height); if (!rowStyleStringMap.containsKey(rowStyle)) { String key = "ro" + rowStyleStringMap.size(); out.writeStartElement("style:style"); out.writeAttribute("style:family", "table-row"); out.writeAttribute("style:name", key); out.writeStartElement("style:table-row-properties"); out.writeAttribute("style:row-height", height.toString() + "mm"); out.writeEndElement(); out.writeEndElement(); rowStyleStringMap.put(rowStyle, key); } } private void writeTableStyle(XMLStreamWriter out, Sheet sheet) throws XMLStreamException { TableStyle tableStyle = new TableStyle(); tableStyle.setHidden(sheet.isHidden()); if (!tableStyleStringMap.containsKey(tableStyle)) { String key = "tb" + tableStyleStringMap.size(); out.writeStartElement("style:style"); out.writeAttribute("style:family", "table"); out.writeAttribute("style:name", key); out.writeStartElement("style:table-properties"); out.writeAttribute("table:display", tableStyle.isHidden() ? "false" : "true"); out.writeEndElement(); out.writeEndElement(); tableStyleStringMap.put(tableStyle, key); } } private void writeBorderStyle(XMLStreamWriter out, Style style) throws XMLStreamException { Borders borders = style.getBorders(); if (borders.isBorder()) { out.writeAttribute("fo:border", borders.getBorderProperties()); } if (borders.isBorderTop()) { out.writeAttribute("fo:border-top", borders.getBorderTopProperties()); } if (borders.isBorderBottom()) { out.writeAttribute("fo:border-bottom", borders.getBorderBottomProperties()); } if (borders.isBorderLeft()) { out.writeAttribute("fo:border-left", borders.getBorderLeftProperties()); } if (borders.isBorderRight()) { out.writeAttribute("fo:border-right", borders.getBorderRightProperties()); } } }