/**
* <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.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipOutputStream;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.openxml.workbookstyle.CellStyle;
/**
*
* Initial date: 21.04.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class OpenXMLWorksheet {
private static final OLog log = Tracing.createLoggerFor(OpenXMLWorksheet.class);
static private char[] COLUMNS;
static {
COLUMNS = new char[26];
for (int i = 0; i < COLUMNS.length; i++) {
COLUMNS[i] = (char) (i + 'A');
}
}
private String id;
private final OpenXMLWorkbook workbook;
private final ZipOutputStream zout;
private XMLStreamWriter writer;
private int headerRows = 0;
private boolean opened = false;
private final Calendar cal = Calendar.getInstance();
private Row row;
private int rowPosition = 0;
public OpenXMLWorksheet(String id, OpenXMLWorkbook workbook, ZipOutputStream zout) {
this.id = id;
this.zout = zout;
this.workbook = workbook;
}
public String getId() {
return id;
}
public int getHeaderRows() {
return headerRows;
}
public void setHeaderRows(int headerRows) {
this.headerRows = headerRows;
}
public Row newRow() {
if(!opened) {
appendProlog();
opened = true;
}
if(row != null) {
appendRow();
row = null;
}
row = new Row();
rowPosition++;
return row;
}
protected void close() {
if(!opened) {
appendProlog();
}
if(row != null) {
appendRow();
row = null;
}
appendAfterlog();
}
private void appendRow() {
if(row == null || row.isEmpty()) return;
try {
String rowId = Integer.toString(rowPosition);
writer.writeStartElement("row");
writer.writeAttribute("r", rowId);
int numOfCols = row.size();
for(int j=0; j<numOfCols; j++) {
Cell cell = row.getCell(j);
if(cell != null && cell.getValue() != null) {
writer.writeStartElement("c");
writer.writeAttribute("r", getColumn(j).concat(rowId));
CellStyle style = cell.getStyle();
if(style != null && style.getIndex() > 0) {
writer.writeAttribute("s", Integer.toString(style.getIndex()));
}
if(cell.getType() == CellType.sharedString) {
writer.writeAttribute("t", "s");
}
writer.writeStartElement("v");
if(cell.getType() == CellType.date) {
cal.setTime((Date)cell.getValue());
double val = internalGetExcelDate(cal, false);
writer.writeCharacters(Double.toString(val));
} else {
writer.writeCharacters(cell.getValue().toString());
}
writer.writeEndElement();// end v
writer.writeEndElement();// end c
}
}
writer.writeEndElement();//end row
} catch (XMLStreamException e) {
log.error("", e);
}
}
/*
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
<dimension ref="A1:D4" />
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0">
<selection activeCell="E3" sqref="E3" />
<pane topLeftCell="A2" xSplit="0" ySplit="2" activePane="bottomLeft" state="frozen" />
</sheetView>
</sheetViews>
<sheetFormatPr baseColWidth="10" defaultRowHeight="15" x14ac:dyDescent="0" />
<sheetData>
</sheetData>
<pageMargins left="0.75" right="0.75" top="1" bottom="1" header="0.5" footer="0.5" />
<extLst>
<ext uri="{64002731-A6B0-56B0-2670-7721B7C09600}" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main">
<mx:PLV Mode="0" OnePage="0" WScale="0" />
</ext>
</extLst>
</worksheet>
*/
private void appendProlog() {
try {
writer = OpenXMLUtils.createStreamWriter(zout);
writer.writeStartDocument("UTF-8", "1.0");
writer.writeStartElement("worksheet");
writer.writeNamespace("", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
writer.writeNamespace("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
writer.writeNamespace("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006");
writer.writeAttribute("mc:Ignorable", "x14ac");
writer.writeNamespace("x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
//sheetViews
writer.writeStartElement("sheetViews");
writer.writeStartElement("sheetView");
writer.writeAttribute("tabSelected", "1");
writer.writeAttribute("workbookViewId", "0");
//header rows
if(getHeaderRows() > 0) {
writer.writeStartElement("pane");
writer.writeAttribute("topLeftCell", "A" + (getHeaderRows() + 1));
writer.writeAttribute("xSplit", "0");
writer.writeAttribute("ySplit", Integer.toString(getHeaderRows()));
writer.writeAttribute("activePane", "bottomLeft");
writer.writeAttribute("state", "frozen");
writer.writeEndElement();
}
writer.writeStartElement("selection");
writer.writeAttribute("activeCell", "A1");
writer.writeAttribute("sqref", "A1");
writer.writeEndElement();
writer.writeEndElement();//end sheetView
writer.writeEndElement();// end sheetViews
//sheet format
writer.writeStartElement("sheetFormatPr");
writer.writeAttribute("baseColWidth", "10");
writer.writeAttribute("defaultRowHeight", "15");
writer.writeAttribute("x14ac:dyDescent", "0");
writer.writeEndElement();
writer.writeStartElement("sheetData");
} catch(XMLStreamException e) {
log.error("", e);
}
}
private void appendAfterlog() {
try {
writer.writeEndElement();//end sheetData
//page margins
writer.writeStartElement("pageMargins");
writer.writeAttribute("left", "0.75");
writer.writeAttribute("right", "0.75");
writer.writeAttribute("top", "1");
writer.writeAttribute("bottom", "1");
writer.writeAttribute("header", "0.5");
writer.writeAttribute("footer", "0.5");
writer.writeEndElement();
//extLst
writer.writeStartElement("extLst");
writer.writeStartElement("ext");
writer.writeAttribute("uri", "{64002731-A6B0-56B0-2670-7721B7C09600}");
writer.writeNamespace("mx", "http://schemas.microsoft.com/office/mac/excel/2008/main");
writer.writeStartElement("mx:PLV");
writer.writeAttribute("Mode", "0");
writer.writeAttribute("OnePage", "0");
writer.writeAttribute("WScale", "0");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndElement();// end worksheet
writer.writeEndDocument();
writer.flush();
writer.close();
} catch (XMLStreamException e) {
log.error("", e);
}
}
public static final int SECONDS_PER_DAY = (24 * 60 * 60);
public static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L;
private static double internalGetExcelDate(Calendar date, boolean use1904windowing) {
// Because of daylight time saving we cannot use
// date.getTime() - calStart.getTimeInMillis()
// as the difference in milliseconds between 00:00 and 04:00
// can be 3, 4 or 5 hours but Excel expects it to always
// be 4 hours.
// E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours
// and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours
double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 + date.get(Calendar.MINUTE)) * 60 + date.get(Calendar.SECOND)) * 1000 + date.get(Calendar.MILLISECOND))
/ ( double ) DAY_MILLISECONDS;
Calendar calStart = dayStart(date);
double value = fraction + absoluteDay(calStart, use1904windowing);
if (!use1904windowing && value >= 60) {
value++;
} else if (use1904windowing) {
value--;
}
return value;
}
private static Calendar dayStart(final Calendar cal){
cal.get(Calendar.HOUR_OF_DAY); // force recalculation of internal fields
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.get(Calendar.HOUR_OF_DAY); // force recalculation of internal fields
return cal;
}
protected static int absoluteDay(Calendar cal, boolean use1904windowing) {
return cal.get(Calendar.DAY_OF_YEAR) + daysInPriorYears(cal.get(Calendar.YEAR), use1904windowing);
}
private static int daysInPriorYears(int yr, boolean use1904windowing) {
int yr1 = yr - 1;
int leapDays = yr1 / 4 // plus julian leap days in prior years
- yr1 / 100 // minus prior century years
+ yr1 / 400 // plus years divisible by 400
- 460; // leap days in previous 1900 years
return 365 * (yr - (use1904windowing ? 1904 : 1900)) + leapDays;
}
/**
* Thanks to POI project and the argument c is zero based.
* @param c The column position, start with zero
* @return
*/
protected static final String getColumn(int c) {
int excelColNum = c + 1;
StringBuilder colRef = new StringBuilder(3);
int colRemain = excelColNum;
while(colRemain > 0) {
int thisPart = colRemain % 26;
if(thisPart == 0) {
thisPart = 26;
}
colRemain = (colRemain - thisPart) / 26;
char colChar = (char)(thisPart + 64);// A is at 65
colRef.insert(0, colChar);
}
return colRef.toString();
}
public class Row {
private List<Cell> cells = new ArrayList<>(255);
public boolean isEmpty() {
return cells == null || cells.isEmpty();
}
public int size() {
return cells.size();
}
public Cell getCell(int column) {
if(cells.size() > column) {
return cells.get(column);
}
return null;
}
public Cell addCell(int column, String value) {
return addCell(column, value, null);
}
public Cell addCell(int column, String value, CellStyle style) {
Cell cell = getOrCreateCell(column);
cell.setStyle(style);
cell.setType(CellType.sharedString);
if(value != null) {
int sharedIndex = workbook.getSharedStrings().add(value);
if(sharedIndex >= 0) {
cell.setValue(sharedIndex);
}
}
return cell;
}
public Cell addCell(int column, Number value, CellStyle style) {
Cell cell = getOrCreateCell(column);
cell.setStyle(style);
cell.setType(CellType.number);
cell.setValue(value);
return cell;
}
/**
* Add a date to the cell, please choose a style with date / duration formatting.
* @param column
* @param value
* @param style
* @return
*/
public Cell addCell(int column, Date value, CellStyle style) {
Cell cell = getOrCreateCell(column);
cell.setStyle(style);
cell.setType(CellType.date);
cell.setValue(value);
return cell;
}
private Cell getOrCreateCell(int column) {
Cell c;
if(cells.size() < column) {
for(int i=cells.size(); i<column; i++) {
cells.add(null);
}
c = new Cell();
cells.add(c);
} else if(cells.size() == column) {
c = new Cell();
cells.add(c);
} else if(cells.get(column) == null) {
c = new Cell();
cells.set(column, c);
} else {
c = cells.get(column);
}
return c;
}
}
public static class Cell {
private Object value;
private CellType type;
private CellStyle style;
public Cell() {
//
}
public Cell(Object value, CellType type, CellStyle style) {
this.value = value;
this.type = type;
this.style = style;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public CellType getType() {
return type;
}
public void setType(CellType type) {
this.type = type;
}
public CellStyle getStyle() {
return style;
}
public void setStyle(CellStyle style) {
this.style = style;
}
}
public enum CellType {
number,
sharedString,
date
}
}