From 9da7004c6b87d92a13e67ab8eb96434c70c067a1 Mon Sep 17 00:00:00 2001 From: jomu Date: Wed, 22 Aug 2018 10:52:20 +0200 Subject: [PATCH] generalized poi workbook handler --- poi-util/pom.xml | 38 +++ .../muehlencord/shared/poi/WorkbookApp.java | 293 ++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 poi-util/pom.xml create mode 100644 poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java diff --git a/poi-util/pom.xml b/poi-util/pom.xml new file mode 100644 index 0000000..3cb88b7 --- /dev/null +++ b/poi-util/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + de.muehlencord.shared + shared-poi-util + 1.1-SNAPSHOT + jar + + + de.muehlencord + shared + 1.1-SNAPSHOT + + + shared-poi-util + + + + + org.apache.poi + poi-ooxml + + + de.muehlencord.shared + shared-util + 1.1-SNAPSHOT + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + \ No newline at end of file diff --git a/poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java b/poi-util/src/main/java/de/muehlencord/shared/poi/WorkbookApp.java new file mode 100644 index 0000000..c97447c --- /dev/null +++ b/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; + } + +}