diff --git a/.gitignore b/.gitignore
index c02e72a..5667ee1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,5 @@
/jeeutil/target/
/account/target/
/shiro-faces/target/
-/pdf/target/
\ No newline at end of file
+/pdf/target/
+/shared-poi-util/target/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 630b069..f67650a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
account
pdf
shiro-faces
+ shared-poi-util
@@ -156,7 +157,12 @@
javax.el-api
3.0.0
provided
-
+
+
+ org.apache.poi
+ poi-ooxml
+ 3.17
+
diff --git a/shared-poi-util/pom.xml b/shared-poi-util/pom.xml
new file mode 100644
index 0000000..904aaea
--- /dev/null
+++ b/shared-poi-util/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ de.muehlencord.shared
+ shared-poi-util
+ 1.0-SNAPSHOT
+ jar
+
+ shared-poi-util
+
+
+ de.muehlencord
+ shared
+ 1.0-SNAPSHOT
+
+
+
+
+ org.apache.poi
+ poi-ooxml
+
+
+ de.muehlencord.shared
+ shared-util
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
\ No newline at end of file
diff --git a/shared-poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java b/shared-poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java
new file mode 100644
index 0000000..c97447c
--- /dev/null
+++ b/shared-poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java
@@ -0,0 +1,293 @@
+package de.muehlencord.shared.poi;
+
+import de.muehlencord.shared.util.file.FileUtil;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.formula.FormulaParser;
+import org.apache.poi.ss.formula.FormulaRenderer;
+import org.apache.poi.ss.formula.FormulaType;
+import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.formula.ptg.RefPtgBase;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataValidation;
+import org.apache.poi.ss.usermodel.DataValidationHelper;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+public class WorkbookApp {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(WorkbookApp.class);
+
+ /**
+ * the workbook to work on
+ */
+ protected Workbook wb;
+
+ /**
+ * opens the given workbook
+ *
+ * @param filename path and filename of the workbook to open.
+ * @return the workbook loaded
+ * @throws FileNotFoundException if the workbook cannot be found
+ * @throws IOException if the workbook cannot be loaded
+ */
+ public Workbook loadWorkbook(String filename) throws IOException {
+ if (filename.toLowerCase().endsWith(".xlsx")) {
+ FileInputStream fis = new FileInputStream(new File(filename));
+ return new XSSFWorkbook(fis);
+ } else {
+ POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(filename));
+ return new HSSFWorkbook(fs, true);
+ }
+ }
+
+ /**
+ * stores the (changed) workbook under the given filename
+ *
+ * @param filename the path and name of the file to store the workbook
+ * under.
+ * @throws FileNotFoundException if the path is not found
+ * @throws IOException if the file cannot be genrated.
+ */
+ public void storeWorkbook(String filename) throws IOException {
+ FileOutputStream fos = new FileOutputStream(new File(filename));
+ wb.write(fos);
+ }
+
+ /**
+ * stores the (changed) workbook under the given filename
+ *
+ * @param fileName the path and name of the file to store the workbook
+ * under.
+ * @param tempName a temporary name the file is stored under before it is
+ * renamed to fileName. This is required as POI cannot open and store a
+ * Workbook under the same name.
+ * @throws FileNotFoundException if the path is not found
+ * @throws IOException if the file cannot be generated.
+ */
+ public void storeWorkbook(String fileName, String tempName) throws IOException {
+ FileOutputStream fos = new FileOutputStream(new File(tempName));
+ wb.write(fos);
+
+ File source = Paths.get(tempName).toFile();
+ File destination = Paths.get(fileName).toFile();
+ FileUtil.moveFileTo(source, destination);
+ }
+
+ protected void copyColumn(Sheet oldSheet, int oldColumn, Sheet newSheet, int newColumn) {
+ int maxRowNum = oldSheet.getLastRowNum();
+ for (int i = 0; i <= maxRowNum; i++) {
+ Row currentRow = oldSheet.getRow(i);
+ if (currentRow != null) {
+ Cell sourceCell = currentRow.getCell(oldColumn);
+ Cell destCell = currentRow.createCell(newColumn);
+ copyCell(sourceCell, destCell, null); // copy to same sheet, cellStyle map not needed
+
+ CellRangeAddress oldRegion = getMergedRegion(oldSheet, i, oldColumn);
+ CellRangeAddress newRegion = getMergedRegion(newSheet, i, newColumn);
+ if (oldRegion != null && newRegion == null) {
+
+ if (oldSheet != newSheet | oldRegion.getFirstColumn() == oldColumn) {
+ // region starts in this column; create new one
+ int firstRow = oldRegion.getFirstRow();
+ int lastRow = oldRegion.getLastRow();
+ int firstColumn = oldRegion.getFirstColumn() + newColumn - oldColumn;
+ int lastColumn = oldRegion.getLastColumn() + newColumn - oldColumn;
+
+ newRegion = new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn);
+ newSheet.addMergedRegion(newRegion);
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Created new merged region {}", newRegion.toString());
+ }
+ } else {
+ // region contains source column, extend column to including new column
+ Integer mergedRegionIndex = getMergedRegionIndex(oldSheet, oldRegion);
+ if (mergedRegionIndex != null) {
+ oldSheet.removeMergedRegion(mergedRegionIndex);
+ }
+
+ int firstRow = oldRegion.getFirstRow();
+ int lastRow = oldRegion.getLastRow();
+ int firstColumn = oldRegion.getFirstColumn();
+ int lastColumn = newColumn;
+
+ newRegion = new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn);
+ newSheet.addMergedRegion(newRegion);
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Updated merged region from {} to ", oldRegion.toString(), newRegion.toString());
+ }
+ }
+ }
+ }
+ }
+ newSheet.setColumnWidth(newColumn, oldSheet.getColumnWidth(oldColumn));
+ }
+
+ protected void copyCell(Cell oldCell, Cell newCell, Map styleMap) {
+ if (oldCell.getSheet().getWorkbook() == newCell.getSheet().getWorkbook()) {
+ newCell.setCellStyle(oldCell.getCellStyle());
+ } else if (styleMap != null) {
+ int stHashCode = oldCell.getCellStyle().hashCode();
+ CellStyle newCellStyle = styleMap.get(stHashCode);
+ if (newCellStyle == null) {
+ newCellStyle = newCell.getSheet().getWorkbook().createCellStyle();
+ newCellStyle.cloneStyleFrom(oldCell.getCellStyle());
+ styleMap.put(stHashCode, newCellStyle);
+ }
+ newCell.setCellStyle(newCellStyle);
+ }
+
+ switch (oldCell.getCellTypeEnum()) {
+ case STRING:
+ newCell.setCellValue(oldCell.getStringCellValue());
+ break;
+ case NUMERIC:
+ newCell.setCellValue(oldCell.getNumericCellValue());
+ break;
+ case BLANK:
+ newCell.setCellType(CellType.BLANK);
+ break;
+ case BOOLEAN:
+ newCell.setCellValue(oldCell.getBooleanCellValue());
+ break;
+ case ERROR:
+ newCell.setCellErrorValue(oldCell.getErrorCellValue());
+ break;
+ case FORMULA:
+ String formula = oldCell.getCellFormula();
+ XSSFEvaluationWorkbook workbookWrapper = XSSFEvaluationWorkbook.create((XSSFWorkbook) wb);
+ /* parse formula */
+ Ptg[] ptgs = FormulaParser.parse(formula, workbookWrapper, FormulaType.CELL, 0 /*sheet index*/);
+ /* re-calculate cell references */
+ for (Ptg ptg : ptgs) {
+ //base class for cell reference "things"
+ if (ptg instanceof RefPtgBase) {
+ RefPtgBase ref = (RefPtgBase) ptg;
+ if (ref.isColRelative()) {
+ ref.setColumn(ref.getColumn() + newCell.getColumnIndex() - oldCell.getColumnIndex());
+ }
+ if (ref.isRowRelative()) {
+ ref.setRow(ref.getRow() + +newCell.getRowIndex() - oldCell.getRowIndex());
+ }
+ }
+ }
+ formula = FormulaRenderer.toFormulaString(workbookWrapper, ptgs);
+ newCell.setCellFormula(formula);
+ break;
+ default:
+ break;
+ }
+
+ XSSFSheet sheet = (XSSFSheet) oldCell.getSheet();
+
+ // TODO copy conditional formating
+ // FIXME - does not work, it does not take care about formulas at the moment
+ /*
+
+ XSSFSheetConditionalFormatting conditionalFormatting = sheet.getSheetConditionalFormatting();
+ int countConditionalFormatting = conditionalFormatting.getNumConditionalFormattings();
+ for (int i = 0; i < countConditionalFormatting; i++) {
+ XSSFConditionalFormatting currentFormatting = conditionalFormatting.getConditionalFormattingAt(i);
+ CellRangeAddress[] cellRangeAddresses = currentFormatting.getFormattingRanges();
+ boolean oldCellHasConditionalFormatting = false;
+ for (CellRangeAddress currentCellRange : cellRangeAddresses) {
+ oldCellHasConditionalFormatting = currentCellRange.containsRow(oldCell.getRowIndex()) && currentCellRange.containsColumn(oldCell.getColumnIndex());
+ }
+ if (oldCellHasConditionalFormatting) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Found conditional formatting for cell {} ", oldCell.getAddress().toString());
+ }
+
+
+
+ // add the new cell to the cell rang addresses
+ CellRangeAddress[] newCellRangeAddresses = new CellRangeAddress[cellRangeAddresses.length+1];
+ for (int j = 0; j < cellRangeAddresses.length; j++) {
+ newCellRangeAddresses[j] = cellRangeAddresses[j];
+ }
+ newCellRangeAddresses[cellRangeAddresses.length] = new CellRangeAddress (newCell.getRowIndex(), newCell.getRowIndex(), newCell.getColumnIndex(), newCell.getColumnIndex());
+ currentFormatting.setFormattingRanges(newCellRangeAddresses);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Copied conditional formatting to cell {}", newCell.getAddress().toString());
+ }
+
+ }
+ }
+ */
+ // SheetConditionalFormatting cf = oldCell.getSheet().getSheetConditionalFormatting();
+ // copy data constraints
+ List dataValidations = sheet.getDataValidations();
+ DataValidationHelper dataValidationHelper = sheet.getDataValidationHelper();
+
+ Iterator it = dataValidations.iterator();
+
+ while (it.hasNext()) {
+ XSSFDataValidation dataValidation = it.next();
+ boolean oldCellHasDataValidation = false;
+ CellRangeAddress[] cellRangeAddresses = dataValidation.getRegions().getCellRangeAddresses();
+ for (CellRangeAddress currentCellRange : cellRangeAddresses) {
+ oldCellHasDataValidation = currentCellRange.containsRow(oldCell.getRowIndex()) && currentCellRange.containsColumn(oldCell.getColumnIndex());
+ }
+ if (oldCellHasDataValidation) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Found data validation for cell {} ", oldCell.getAddress().toString());
+ }
+
+ CellRangeAddressList newCellRangeList = new CellRangeAddressList(newCell.getRowIndex(), newCell.getRowIndex(), newCell.getColumnIndex(), newCell.getColumnIndex());
+ DataValidation newValidation = dataValidationHelper.createValidation(dataValidation.getValidationConstraint(), newCellRangeList);
+ sheet.addValidationData(newValidation);
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Copied data validation to cell {}", newCell.getAddress().toString());
+ }
+ }
+ }
+ }
+
+ protected CellRangeAddress getMergedRegion(Sheet sheet, int rowNum, int cellNum) {
+ for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+ CellRangeAddress merged = sheet.getMergedRegion(i);
+ if (merged.isInRange(rowNum, cellNum)) {
+ return merged;
+ }
+ }
+ return null;
+ }
+
+ protected Integer getMergedRegionIndex(Sheet sheet, CellRangeAddress mergedRegion) {
+ for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+ CellRangeAddress merged = sheet.getMergedRegion(i);
+ if ((merged.getFirstColumn() == mergedRegion.getFirstColumn())
+ && (merged.getLastColumn() == mergedRegion.getLastColumn())
+ && (merged.getFirstRow() == mergedRegion.getFirstRow())
+ && (merged.getLastRow() == mergedRegion.getLastRow())) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+}