From 8a8c6531ffc895d1499eacbdb132512eabf76ffc Mon Sep 17 00:00:00 2001 From: Fabio Heer Date: Tue, 8 Aug 2017 16:24:46 +0200 Subject: [PATCH 1/3] Import OSS version --- .editorconfig | 21 + .gitattributes | 4 + .gitignore | 10 + CHANGELOG.md | 82 ++++ dvbern-lib-excelmerger-impl/pom.xml | 66 +++ .../dvbern/oss/lib/excelmerger/Context.java | 156 +++++++ .../oss/lib/excelmerger/ExcelConverter.java | 25 ++ .../lib/excelmerger/ExcelMergeException.java | 27 ++ .../ExcelMergeRuntimeException.java | 27 ++ .../oss/lib/excelmerger/ExcelMerger.java | 391 ++++++++++++++++++ .../oss/lib/excelmerger/ExcelMergerDTO.java | 104 +++++ .../ExcelTemplateParseException.java | 30 ++ .../oss/lib/excelmerger/GroupPlaceholder.java | 54 +++ .../oss/lib/excelmerger/Placeholder.java | 72 ++++ .../dvbern/oss/lib/excelmerger/PoiUtil.java | 174 ++++++++ .../lib/excelmerger/StringColorCellDTO.java | 52 +++ .../lib/excelmerger/converters/Converter.java | 41 ++ .../excelmerger/converters/ConverterUtil.java | 94 +++++ .../converters/ParametrisedConverter.java | 22 + .../converters/StandardConverters.java | 179 ++++++++ .../mergefields/DefaultMergeField.java | 71 ++++ .../excelmerger/mergefields/MergeField.java | 74 ++++ .../mergefields/MergeFieldProvider.java | 35 ++ .../mergefields/RepeatColMergeField.java | 29 ++ .../mergefields/RepeatRowMergeField.java | 28 ++ .../mergefields/RepeatValMergeField.java | 28 ++ .../mergefields/SimpleMergeField.java | 28 ++ .../oss/lib/excelmerger/CopyRowTest.java | 188 +++++++++ .../ExcelMergerMergeGroupTest.java | 329 +++++++++++++++ .../oss/lib/excelmerger/ExcelMergerTest.java | 198 +++++++++ .../lib/excelmerger/ExcelMergerTestUtil.java | 95 +++++ .../excelmerger/MergeFieldBelegungsplan.java | 181 ++++++++ .../oss/lib/excelmerger/MergeFieldTest.java | 54 +++ .../lib/excelmerger/MergeFieldWarteliste.java | 117 ++++++ .../dvbern/oss/lib/excelmerger/Procedure.java | 21 + .../excelmerger/StandardConvertersTest.java | 65 +++ .../oss/lib/excelmerger/belegungsplan.xlsx | Bin 0 -> 16204 bytes .../copyGroupWithMergedRegion.xlsx | Bin 0 -> 4894 bytes .../excelmerger/copyRowWithMergedRegion.xlsx | Bin 0 -> 4847 bytes .../excelmerger/copyRowWithNamedRegion.xlsx | Bin 0 -> 5030 bytes .../lib/excelmerger/shiftDataValidations.xlsx | Bin 0 -> 9924 bytes .../oss/lib/excelmerger/warteliste.xlsx | Bin 0 -> 11037 bytes .../src/test/resources/log4j.properties | 23 ++ pom.xml | 149 +++++++ 44 files changed, 3344 insertions(+) create mode 100755 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 dvbern-lib-excelmerger-impl/pom.xml create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Context.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelConverter.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeException.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeRuntimeException.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMerger.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerDTO.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelTemplateParseException.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/GroupPlaceholder.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Placeholder.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/PoiUtil.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/StringColorCellDTO.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/Converter.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ConverterUtil.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ParametrisedConverter.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/StandardConverters.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/DefaultMergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeFieldProvider.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatColMergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatRowMergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatValMergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/SimpleMergeField.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/CopyRowTest.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerMergeGroupTest.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTest.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTestUtil.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldBelegungsplan.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldTest.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldWarteliste.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/Procedure.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/StandardConvertersTest.java create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/belegungsplan.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/copyGroupWithMergedRegion.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/copyRowWithMergedRegion.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/copyRowWithNamedRegion.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/shiftDataValidations.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/warteliste.xlsx create mode 100644 dvbern-lib-excelmerger-impl/src/test/resources/log4j.properties create mode 100644 pom.xml diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..d256074 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org +root = true + +[*.{java,xml}] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Kompatibility zu maven-dependency-plugin/maven-release-plugin... und damit zu jgitflow +[pom.xml] +indent_style = space + +[*.yml] +indent_style = space +indent_size = 2 + +[*.sh] +end_of_line = lf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bd6cb5f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.sh text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82177a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +#LibreOffice/Excel Lockfiles (z.B. beim Bearbeiten von basisreport.xls) +.~lock* +.access^ + +### Maven +target/ + +### Intellij ### +.idea/ +*.iml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e20c2ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# 2.0.0 Typing support + +## License change +We are open sourcing! This library is now available under the Apache License, Version 2.0 + +## POI Update +Upgrade from 3.15 to 3.16 + +## Breaking change +Deprecated code has been removed. + +MergeFields and Converters are now using property typings, such that you cannot set mistakingly +the wrong merge value. + +If you used excel Merger before, you will have to adjust your MergeField enums. +You can Use the following regular expressions to adjust to the new api. + +`` +(\w+)\((\w+), Type.SIMPLE\) +$1(new SimpleMergeField<>("$1", $2)) +`` + +`` +(\w+)\((\w+), Type.REPEAT_COL\) +$1(new RepeatColMergeField<>("$1", $2)) +`` + +`` +(\w+)\((\w+), Type.REPEAT_VAL\) +$1(new RepeatValMergeField<>("$1", $2)) +`` + +`` +(\w+)\((\w+), Type.REPEAT_ROW\) +$1(new RepeatRowMergeField("$1")) +`` + +`` +enum (\w+) implements MergeField +enum $1 implements MergeFieldProvider +`` + +`` +import static ch\.dvbern\.lib\.excelmerger\.StandardConverters\.(\w+); +import static ch\.dvbern\.oss\.lib\.excelmerger\.converters\.StandardConverters\.$1; +`` + +`` +import ch\.dvbern\.lib\.excelmerger\.MergeField; +import ch\.dvbern\.oss\.lib\.excelmerger\.mergefields\.MergeField; +`` + +`` +ch\.dvbern\.lib\.excelmerger +ch\.dvbern\.oss\.lib\.excelmerger +`` + +Please adjust your enum constructors and getters manually. + +# 1.1.1 PERCENT_CONVERTER scale issue +- It was possible, that the resulting percentage value was wrongly converted, for +instance when the scale of the input BigDecimal was 0. + +# 1.1.0 FZL (kurstool) integration (2017-03-17) + +## Changes +- INTEGER_CONVERTER has been deprecated because it is actually a LONG_CONVERTER. +Please migrate to LONG_CONVERTER. + +## New Features +- Template-Patters may contain underscore "_" +- **MergeField** + - PAGE_BREAK - can be added in a template to insert a page break +- **Converters** + - STRING_COLORED_CONVERTER - write a string value in the supplied colour + - DATE_WITH_DAY_CONVERTER - prefixes the default date format with the weekday ("EE, dd.MM.yyyy") + - LONG_CONVERTER - superseeds INTEGER_CONVERTER + +## Bugfixes +- slf4j-log4j12 should only be included in test scope + +# 1.0.0 KitAdmin Excel Merger (2017-03-17) diff --git a/dvbern-lib-excelmerger-impl/pom.xml b/dvbern-lib-excelmerger-impl/pom.xml new file mode 100644 index 0000000..fa53db3 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/pom.xml @@ -0,0 +1,66 @@ + + + + + + ch.dvbern.oss.lib.excelmerger + dvbern-lib-excelmerger + 1.1.2-SNAPSHOT + + 4.0.0 + + dvbern-lib-excelmerger-impl + DVBern ExcelMerger Implementation + jar + + + + com.google.code.findbugs + annotations + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + test + + + commons-io + commons-io + + + com.google.guava + guava + + + org.apache.poi + poi + + + org.apache.poi + poi-ooxml + + + junit + junit + test + + + + diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Context.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Context.java new file mode 100644 index 0000000..0a4c0d3 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Context.java @@ -0,0 +1,156 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellCopyPolicy; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class Context { + public static final int BASE_10 = 10; + @Nonnull + private final Workbook workbook; + @Nonnull + private final Sheet sheet; + @Nonnull + private final Map> mergeFields; + + private int currentRow = 0; + + @Nonnull + private CellCopyPolicy cellCopyPolicy = new CellCopyPolicy.Builder() + .build(); + + Context(@Nonnull Workbook workbook, @Nonnull Sheet sheet, @Nonnull Map> mergeFields) { + this(workbook, sheet, mergeFields, sheet.getFirstRowNum()); + } + + Context( + @Nonnull Workbook workbook, + @Nonnull Sheet sheet, + @Nonnull Map> mergeFields, + int startRow) { + + this.workbook = checkNotNull(workbook); + this.sheet = checkNotNull(sheet); + this.mergeFields = checkNotNull(mergeFields); + this.currentRow = startRow; + } + + @Nonnull + public Workbook getWorkbook() { + return workbook; + } + + @Nonnull + public Sheet getSheet() { + return sheet; + } + + public int currentRowNum() { + return currentRow; + } + + @Nonnull + public Row currentRow() { + Row row = sheet.getRow(currentRowNum()); + if (row == null) { + row = sheet.createRow(currentRowNum()); + } + return row; + } + + public void advanceRow() { + currentRow++; + } + + @Nonnull + Optional detectGroup() { + Row row = currentRow(); + + // von hinten nach vorne durcharbeiten + for (int i = Math.max(row.getLastCellNum(), 0); i >= Math.max(row.getFirstCellNum(), 0); i--) { + Cell cell = row.getCell(i); + + Optional groupPlaceholder = parsePlaceholder(cell) + .filter(placeholder -> placeholder instanceof GroupPlaceholder) + .map(placeholder -> (GroupPlaceholder) placeholder); + + if (groupPlaceholder.isPresent()) { + return groupPlaceholder; + } + } + + return Optional.empty(); + } + + @Nonnull + Optional parsePlaceholder(@Nullable Cell cell) { + if (cell == null || cell.getCellTypeEnum() != CellType.STRING) { + return Optional.empty(); + } + + Matcher matcher = ExcelMerger.MERGEFIELD_REX.matcher(cell.getStringCellValue()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String pattern = matcher.group(ExcelMerger.REX_GROUP_PATTERN); + String key = matcher.group(ExcelMerger.REX_GROUP_KEY); + + Integer groupRows = matcher.group(ExcelMerger.REF_GROUP_ROWS) != null ? + Integer.valueOf(matcher.group(ExcelMerger.REF_GROUP_ROWS), BASE_10) : + null; + + MergeField field = mergeFields.get(key); + + if (field == null) { + return Optional.empty(); + } + + if (field.getType() == MergeField.Type.REPEAT_ROW) { + GroupPlaceholder groupPlaceholder = new GroupPlaceholder(cell, pattern, key, field, groupRows); + + return Optional.of(groupPlaceholder); + } + + Placeholder placeholder = new Placeholder(cell, pattern, key, field); + + return Optional.of(placeholder); + } + + @Nonnull + public CellCopyPolicy getCellCopyPolicy() { + return cellCopyPolicy; + } + + public void setCellCopyPolicy(@Nonnull CellCopyPolicy cellCopyPolicy) { + this.cellCopyPolicy = cellCopyPolicy; + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelConverter.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelConverter.java new file mode 100644 index 0000000..36aa942 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelConverter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +import org.apache.poi.ss.usermodel.Sheet; + +public interface ExcelConverter { + + void applyAutoSize(@Nonnull Sheet sheet); +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeException.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeException.java new file mode 100644 index 0000000..fd4fd28 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ExcelMergeException extends Exception { + private static final long serialVersionUID = 7296329429118050852L; + + public ExcelMergeException(@Nonnull String message, @Nullable Throwable cause) { + super(message, cause); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeRuntimeException.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeRuntimeException.java new file mode 100644 index 0000000..44b1b5c --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergeRuntimeException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +public class ExcelMergeRuntimeException extends RuntimeException { + + private static final long serialVersionUID = -698980312745159534L; + + public ExcelMergeRuntimeException(@Nonnull String message, @Nonnull Throwable throwable) { + super(message, throwable); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMerger.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMerger.java new file mode 100644 index 0000000..da9d984 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMerger.java @@ -0,0 +1,391 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import org.apache.commons.io.IOUtils; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +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.usermodel.WorkbookFactory; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static ch.dvbern.oss.lib.excelmerger.PoiUtil.shiftDataValidations; +import static ch.dvbern.oss.lib.excelmerger.PoiUtil.shiftNamedRanges; +import static ch.dvbern.oss.lib.excelmerger.PoiUtil.shiftRowsAndMergedRegions; + +public final class ExcelMerger { + + private static final Logger LOG = LoggerFactory.getLogger(ExcelMerger.class); + + static final Pattern MERGEFIELD_REX = Pattern.compile(".*(\\{([a-zA-Z1-9_]+)(:(\\d))?}).*"); + static final int REX_GROUP_PATTERN = 1; + static final int REX_GROUP_KEY = 2; + static final int REF_GROUP_ROWS = 4; + // nur ein willkuerlicher Counter, damit's kein while(true) geben muss + private static final int MAX_PLACEHOLDERS_PER_CELL = 10; + + private ExcelMerger() { + // utliity class + } + + /** + * Fuellt ein Excel-Sheet mit den uebergebenen Daten aus. + * Das Sheet wird in Repeat-Gruppen aufgeteilt, die auch verschachtelt sein koennen. + * Repeat-Gruppen-Bezeichner ('z.B. {myRepeat}') muessen ein Feld vom Typ {@link MergeField.Type#REPEAT_ROW} sein. + * Normale Felder - (also 1 Wert pro Repeat-Gruppe) sind vom Typ {@link MergeField.Type#SIMPLE}. + * Spalten-Repeater sind vom Typ {@link MergeField.Type#REPEAT_COL}. + * Findet sich in den Daten nicht ausreichend Werte, werden die Spalten ausgeblendet. + * Nuetzlich z.B. in Ueberschriften. + * Werte-Repeater gehoeren zu Spalten-Repeater und sind die Daten zur Ueberschrift. + * Sie unterscheiden sich zu Spalten-Repeatern dadurch, dass sie keine Spalten ausblenden. + * => Spalten-Repeater legen die anzahl sichtbarer Spalten (und ggf. defen Ueberschrift) fest und + * Werte-Repeater sind die dazugehoerigen Daten die m.o.w. vollstaendig sind. + */ + public static void mergeData(@Nonnull Sheet sheet, + @Nonnull MergeField[] fields, + @Nonnull ExcelMergerDTO excelMergerDTO) throws ExcelMergeException { + Objects.requireNonNull(sheet); + Objects.requireNonNull(fields); + Objects.requireNonNull(excelMergerDTO); + + mergeData(sheet, Arrays.asList(fields), excelMergerDTO); + } + + public static void mergeData(@Nonnull Sheet sheet, + @Nonnull List> fields, + @Nonnull ExcelMergerDTO excelMergerDTO) throws ExcelMergeException { + Objects.requireNonNull(sheet); + Objects.requireNonNull(fields); + Objects.requireNonNull(excelMergerDTO); + + Map> fieldMap = fields.stream() + .collect(Collectors.toMap(MergeField::getKey, field -> field)); + + Workbook wb = sheet.getWorkbook(); + Context ctx = new Context(wb, sheet, fieldMap); + + mergeData(excelMergerDTO, ctx); + } + + public static void mergeData(@Nonnull ExcelMergerDTO excelMergerDTO, @Nonnull Context ctx) + throws ExcelMergeException { + + mergeGroup(ctx, Collections.singletonList(excelMergerDTO), ctx.getSheet().getLastRowNum() + 1); + + FormulaEvaluator eval = ctx.getWorkbook().getCreationHelper().createFormulaEvaluator(); + eval.clearAllCachedResultValues(); + eval.evaluateAll(); + + } + + @Nonnull + public static Workbook createWorkbookFromTemplate(@Nonnull InputStream is) throws ExcelTemplateParseException { + Objects.requireNonNull(is); + + try { + InputStream poiCompatibleIS = toSeekable(is); + // POI braucht einen Seekable InputStream + return WorkbookFactory.create(poiCompatibleIS); + + } catch (IOException | InvalidFormatException | RuntimeException e) { + throw new ExcelTemplateParseException("Error parsing template", e); + } + } + + @FunctionalInterface + private interface TetraConsumer { + void accept(T a, U b, V c, S s) throws ExcelMergeException; + } + + @FunctionalInterface + interface GroupMerger extends TetraConsumer, Row> { + + } + + @Nonnull + private static InputStream toSeekable(@Nonnull InputStream is) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IOUtils.copy(is, baos); + baos.flush(); + + return new ByteArrayInputStream(baos.toByteArray()); + } + + private static void mergeRow(@Nonnull Context ctx, @Nonnull ExcelMergerDTO data) { + Map, Integer> valueOffsets = new HashMap<>(); + Row row = ctx.currentRow(); + int start = Math.max(row.getFirstCellNum(), 0); + int end = Math.max(row.getLastCellNum(), 0); + + IntStream.rangeClosed(start, end) + .mapToObj(row::getCell) + .filter(Objects::nonNull) + .forEach(cell -> mergePlaceholders(ctx, data, valueOffsets, cell)); + } + + private static void mergePlaceholders( + @Nonnull Context ctx, + @Nonnull ExcelMergerDTO data, + @Nonnull Map, Integer> valueOffsets, @Nonnull Cell cell) { + + for (int i = 0; i < MAX_PLACEHOLDERS_PER_CELL; i++) { + Optional placeholderOpt = ctx.parsePlaceholder(cell); + if (!placeholderOpt.isPresent()) { + break; // gibt keine Placeholder, da kann sofort abgebrochen werden + } + + MergeField field = placeholderOpt.get().getField(); + String pattern = placeholderOpt.get().getPattern(); + + if (MergeField.Type.PAGE_BREAK == field.getType()) { + int rowNum = cell.getRow().getRowNum(); + field.getConverter().setCellValue(cell, pattern, null); + ctx.getSheet().setRowBreak(rowNum); + break; + } + + if (!field.getType().doMergeValue()) { + break; + } + + Integer valueOffset = 0; + if (field.getType().doConsumeValue()) { + // erhöht den valueOffset (repeat Felder) + valueOffsets.compute(field, (key, oldVal) -> oldVal == null ? 0 : oldVal + 1); + valueOffset = valueOffsets.get(field); + } + if (data.hasValue(field, valueOffset)) { + // Schreibt den Wert + Object value = data.getValue(field, valueOffset); + field.getConverter().setCellValue(cell, pattern, value); + } else { + field.getConverter().setCellValue(cell, pattern, null); + // Spalte ausblenden + if (field.getType().doHideColumnOnEmpty()) { + ctx.getSheet().setColumnHidden(cell.getColumnIndex(), true); + } + } + } + } + + private static void mergeGroup(@Nonnull Context ctx, @Nonnull List groupRows, int rowSize) + throws ExcelMergeException { + + for (ExcelMergerDTO dto : groupRows) { + for (int rowNum = 0; rowNum < rowSize; rowNum++) { + try { + Row row = ctx.currentRow(); + + Optional group = ctx.detectGroup(); + if (group.isPresent()) { + mergeGroup(ctx, group.get(), dto, row, ExcelMerger::mergeSubGroup); + } else { + mergeRow(ctx, dto); + ctx.advanceRow(); + } + } catch (RuntimeException rte) { + throw new ExcelMergeException("Caught error in sheet " + + ctx.getSheet().getSheetName() + + " on row/col: " + + ctx.currentRowNum(), rte); + } + } + } + } + + static void mergeGroup(@Nonnull Context ctx, + @Nonnull GroupPlaceholder group, + @Nonnull ExcelMergerDTO dto, + @Nonnull Row currentRow, + @Nonnull GroupMerger merger) throws ExcelMergeException { + + List subGroups = dto.getGroup(group.getField()); + group.getCell().setCellValue((String) null); // Group-Repeat-Info aus der Zelle loeschen + if (subGroups == null) { + mergeRow(ctx, dto); + ctx.advanceRow(); + } else { + merger.accept(ctx, group, subGroups, currentRow); + } + } + + static void mergeSubGroup(@Nonnull Context ctx, + @Nonnull GroupPlaceholder group, + @Nonnull List subGroups, + @Nonnull Row currentRow) throws ExcelMergeException { + + duplicateRowsWithStylesMultipleRowShift(ctx, currentRow, group.getRows(), subGroups.size()); + mergeGroup(ctx, subGroups, group.getRows()); + } + + /** + * Dupliziert Rows: + * 1. Platz machen fuer die neuen Rows (i.E.: shift rows) + * 2. Zellen inkl. Styles kopieren + * 3. Ggf. Named-Ranges um die neuen Zeilen erweitern + */ + private static void duplicateRowsWithStylesMultipleRowShift( + @Nonnull Context ctx, + @Nonnull Row startRow, + @Nonnull Integer anzSrcRows, + @Nonnull Integer anzGroups) { + + final int startNeuerBereich = startRow.getRowNum() + anzSrcRows; + final int anzRows = anzSrcRows * (anzGroups - 1); + + boolean isXSSFSheet = ctx.getSheet() instanceof XSSFSheet; + + // + 1 ist wichtig, sonst verschwindet beim Filtern die Total-Zeile + int lastRow = ctx.getSheet().getLastRowNum() + 1; + + // Wenns nach dem zu duplizierenden Bereich noch Zeilen hat: nach unten wegschieben + if (anzRows > 0 && startNeuerBereich <= lastRow) { + if (isXSSFSheet) { + // bei XSSF werden die MergedRegions bei copyRow berücksichtigt, so dass wir hier nur shiften müssen + ctx.getSheet().shiftRows(startNeuerBereich, lastRow, anzRows); + } else { + shiftRowsAndMergedRegions(ctx.getSheet(), startNeuerBereich, lastRow, anzRows); + } + // shiftRows does not shift DataValidations or NamedRanges. We have to shift them manually. + shiftDataValidations(ctx.getSheet(), startNeuerBereich, lastRow + anzRows, anzRows); + shiftNamedRanges(ctx.getSheet(), startRow.getRowNum(), lastRow, anzRows); + } + + // Kopieren + if (isXSSFSheet) { + copyXssfRows(ctx, startRow, anzSrcRows, anzGroups, startNeuerBereich); + } else { + copyRows(ctx, startRow, anzSrcRows, anzGroups, startNeuerBereich); + } + + } + + /** + * Issues + *
    + *
  • Slow!
  • + *
  • Beta
  • + *
  • XSSF dependent
  • + *
+ */ + private static void copyXssfRows( + @Nonnull Context ctx, + @Nonnull Row startRow, + @Nonnull Integer anzSrcRows, + @Nonnull Integer anzGroups, + int startNeuerBereich) { + + XSSFSheet sheet = (XSSFSheet) ctx.getSheet(); + + List rowsToCopy = IntStream.range(0, anzSrcRows) + .mapToObj(i -> sheet.getRow(startRow.getRowNum() + i)) + .collect(Collectors.toList()); + + for (int i = 0; i < anzGroups - 1; i++) { + int startGroup = startNeuerBereich + i * anzSrcRows; + + sheet.copyRows(rowsToCopy, startGroup, ctx.getCellCopyPolicy()); + } + + } + + /** + * Issues + *
    + *
  • Does not shift formula references
  • + * ExcelMergerDTO
  • Does not copy merged cells
  • + *
+ */ + private static void copyRows( + @Nonnull Context ctx, + @Nonnull Row startRow, + @Nonnull Integer anzSrcRows, + @Nonnull Integer anzGroups, + int startNeuerBereich) { + + for (int rowNum = 0; rowNum < anzSrcRows; rowNum++) { + Row srcRow = getRow(ctx.getSheet(), startRow.getRowNum() + rowNum); + + for (int i = 0; i < anzGroups - 1; i++) { + int startGroup = startNeuerBereich + i * anzSrcRows; + Row newRow = getRow(ctx.getSheet(), startGroup + rowNum); + + copyStyles(srcRow, newRow); + } + } + + } + + private static void copyStyles(@Nonnull Row srcRow, @Nonnull Row newRow) { + for (int cellNum = 0; cellNum < srcRow.getLastCellNum(); cellNum++) { + Cell srcCell = srcRow.getCell(cellNum); + if (srcCell != null) { + Cell newCell = getCell(newRow, cellNum); + newCell.setCellStyle(srcCell.getCellStyle()); + switch (srcCell.getCellTypeEnum()) { + case STRING: + newCell.setCellValue(srcCell.getStringCellValue()); + break; + case FORMULA: + newCell.setCellFormula(srcCell.getCellFormula()); + break; + case BLANK: + // nop + break; + default: + LOG.warn("Cell type not supported: {} @{}/{}", srcCell.getCellTypeEnum(), srcCell.getRowIndex(), + srcCell.getColumnIndex()); + } + } + } + } + + @Nonnull + private static Row getRow(@Nonnull Sheet sheet, int index) { + Row row = sheet.getRow(index); + return row == null ? sheet.createRow(index) : row; + } + + @Nonnull + private static Cell getCell(@Nonnull Row row, int column) { + Cell cell = row.getCell(column); + return cell == null ? row.createCell(column) : cell; + } + +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerDTO.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerDTO.java new file mode 100644 index 0000000..ded841e --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerDTO.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField.Type; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeFieldProvider; +import com.google.common.base.Preconditions; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ExcelMergerDTO { + + /** + * Zellen, die im globalen Teil des Excel wiederholt werden (z.B. Ueberschriften mit Firmennamen) + */ + @Nonnull + private final Map, List> values = new HashMap<>(); + private final Map, List> groups = new HashMap<>(); + + @Nonnull + public ExcelMergerDTO createGroup(@Nonnull MergeFieldProvider provider) { + return createGroup(provider.getMergeField()); + } + + @Nonnull + public ExcelMergerDTO createGroup(@Nonnull MergeField group) { + checkNotNull(group); + + Preconditions.checkArgument(group.getType() == Type.REPEAT_ROW, + "Not a REPEAT_ROW type %" + group.getType()); + + List entries = groups.computeIfAbsent(group, key -> new LinkedList<>()); + ExcelMergerDTO newGroup = new ExcelMergerDTO(); + entries.add(newGroup); + + return newGroup; + } + + public void addValue(@Nonnull MergeFieldProvider provider, @Nullable V value) { + addValue(provider.getMergeField(), value); + } + + public void addValue(@Nonnull MergeField mergeField, @Nullable V value) { + checkNotNull(mergeField); + + List valuesList = values.computeIfAbsent(mergeField, key -> new LinkedList<>()); + valuesList.add(value); + } + + @Nullable + public List getGroup(@Nonnull MergeField groupField) { + return groups.get(groupField); + } + + public boolean hasValue(@Nonnull MergeField mergeField, int valueOffset) { + List list = values.get(mergeField); + if (list == null) { + return false; + } + + return valueOffset < list.size(); + } + + @Nullable + public V getValue(@Nonnull MergeField mergeField) { + return getValue(mergeField, 0); + } + + @Nullable + public V getValue(@Nonnull MergeField mergeField, int valueOffset) { + List list = values.get(mergeField); + if (list == null) { + return null; + } + + if (valueOffset < list.size()) { + //noinspection unchecked + return (V) list.get(valueOffset); + } + + return null; + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelTemplateParseException.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelTemplateParseException.java new file mode 100644 index 0000000..6248ec9 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/ExcelTemplateParseException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +public class ExcelTemplateParseException extends RuntimeException { + private static final long serialVersionUID = 8625035601352241549L; + + public ExcelTemplateParseException(@Nonnull String message, @Nonnull Throwable cause) { + super(message, cause); + } + + public ExcelTemplateParseException(@Nonnull String message) { + super(message); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/GroupPlaceholder.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/GroupPlaceholder.java new file mode 100644 index 0000000..8063d87 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/GroupPlaceholder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import com.google.common.base.MoreObjects; +import org.apache.poi.ss.usermodel.Cell; + +class GroupPlaceholder extends Placeholder { + + private final int rows; + + GroupPlaceholder( + @Nonnull Cell cell, + @Nonnull String pattern, + @Nonnull String key, + @Nonnull MergeField field, + @Nullable Integer rowsParsed) { + + super(cell, pattern, key, field); + this.rows = rowsParsed == null ? 1 : rowsParsed; + } + + public int getRows() { + return rows; + } + + @Override + @Nonnull + public String toString() { + return MoreObjects.toStringHelper(this) + .add("pattern", getPattern()) + .add("key", getKey()) + .add("field", getField()) + .add("rows", rows) + .toString(); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Placeholder.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Placeholder.java new file mode 100644 index 0000000..70934f6 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/Placeholder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import com.google.common.base.MoreObjects; +import org.apache.poi.ss.usermodel.Cell; + +import static com.google.common.base.Preconditions.checkNotNull; + +class Placeholder { + @Nonnull + private final Cell cell; + @Nonnull + private final String pattern; + @Nonnull + private final String key; + @Nonnull + private final MergeField field; + + Placeholder(@Nonnull Cell cell, @Nonnull String pattern, @Nonnull String key, @Nonnull MergeField field) { + this.cell = checkNotNull(cell); + this.pattern = checkNotNull(pattern); + this.key = checkNotNull(key); + this.field = field; + } + + @Nonnull + public Cell getCell() { + return cell; + } + + @Nonnull + public String getPattern() { + return pattern; + } + + @Nonnull + public String getKey() { + return key; + } + + @Nonnull + public MergeField getField() { + return field; + } + + @Override + @Nonnull + public String toString() { + return MoreObjects.toStringHelper(this) + .add("pattern", pattern) + .add("key", key) + .add("field", field) + .toString(); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/PoiUtil.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/PoiUtil.java new file mode 100644 index 0000000..ae455cc --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/PoiUtil.java @@ -0,0 +1,174 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; + +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.AreaReference; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.poi.ss.SpreadsheetVersion.EXCEL2007; + +public final class PoiUtil { + + private static final Logger LOG = LoggerFactory.getLogger(PoiUtil.class); + + private static final Pattern CONTAINS_SPACES = Pattern.compile("\\s"); + + private PoiUtil() { + // utility function + } + + /** + * Workaround für POI >= 3.15: Bei shiftRows gehen die MergedRegions (Cell-Verbindungen) verloren. + * + * @see https://bz.apache.org/bugzilla/show_bug + * .cgi?id=60384 + */ + public static void shiftRowsAndMergedRegions(@Nonnull Sheet sheet, int startRow, int endRow, int anzNewRows) { + List mergedRegionsBeforeShift = sheet.getMergedRegions(); + List containedMergedRegions = mergedRegionsBeforeShift.stream() + .filter(cra -> isContained(cra, startRow, endRow)) + .collect(Collectors.toList()); + + // All merged regions within [startRow, startRow + anzNewRows] remain intact, + // but all further merged regions from [startRow + anzNewRows + 1, endRow] disappear due to shiftRows. + sheet.shiftRows(startRow, endRow, anzNewRows); + + List remainingMergedRegions = sheet.getMergedRegions(); + // since the shift was executed, we should check containment with the new endRow: endRow + anzNewRows + int newEndRow = endRow + anzNewRows; + List containedRemainingMergedRegions = sheet.getMergedRegions().stream() + .filter(cra -> isContained(cra, startRow, newEndRow)) + .collect(Collectors.toList()); + + if (mergedRegionsBeforeShift.size() != remainingMergedRegions.size()) { + // restore lost merged regions + containedMergedRegions.stream() + .map(cra -> { + CellRangeAddress copy = cra.copy(); + copy.setFirstRow(cra.getFirstRow() + anzNewRows); + copy.setLastRow(cra.getLastRow() + anzNewRows); + return copy; + }) + .filter(cra -> !containedRemainingMergedRegions.contains(cra)) + .forEach(sheet::addMergedRegion); + } + + if (sheet.getMergedRegions().size() != mergedRegionsBeforeShift.size()) { + LOG.warn("Lost some merged regions in sheet {} when shifting {} rows from {} to {}", + sheet.getSheetName(), anzNewRows, startRow, endRow); + } + } + + public static void shiftDataValidations(@Nonnull Sheet sheet, int startRow, int endRow, int anzNewRows) { + // shift data validations, for example dropdown constraints + List validations = sheet.getDataValidations(); + + if (validations.isEmpty() || !(sheet instanceof XSSFSheet)) { + return; + } + + validations.stream() + .filter(validation -> validation.getValidationConstraint().getExplicitListValues() != null) + .flatMap(validation -> Stream.of(validation.getRegions().getCellRangeAddresses())) + .filter(cra -> isContained(cra, startRow, endRow)) + .forEach(cra -> { + cra.setFirstRow(cra.getFirstRow() + anzNewRows); + cra.setLastRow(cra.getLastRow() + anzNewRows); + }); + + // remove existing data validations + // FIXME do only remove data validations with explicit list values, since these are the only ones we re-create + ((XSSFSheet) sheet).getCTWorksheet().unsetDataValidations(); + + // add the shifted data validations back to the sheet + DataValidationHelper validationHelper = sheet.getDataValidationHelper(); + for (DataValidation validation : validations) { + String[] listValues = validation.getValidationConstraint().getExplicitListValues(); + if (listValues == null) { + continue; + } + DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(listValues); + DataValidation newValidation = validationHelper.createValidation(constraint, validation.getRegions()); + sheet.addValidationData(newValidation); + } + } + + private static boolean isContained(@Nonnull CellRangeAddress cra, int startRow, int endRow) { + return isContained(cra.getFirstRow(), startRow, endRow) && isContained(cra.getLastRow(), startRow, endRow); + } + + private static boolean isContained(int rowNum, int startRow, int endRow) { + return startRow <= rowNum && rowNum <= endRow; + } + + public static void shiftNamedRanges(@Nonnull Sheet sheet, int startRow, int endRow, int anzNewRows) { + sheet.getWorkbook().getAllNames().stream() + .filter(name -> name.getRefersToFormula() != null) + .filter(name -> sheet.getSheetName().equals(name.getSheetName())) + .filter(name -> { + AreaReference areaReference = new AreaReference(name.getRefersToFormula(), EXCEL2007); + return intersects(areaReference, startRow, endRow); + }) + .forEach(name -> shiftNamedRange(name, anzNewRows)); + } + + private static void shiftNamedRange(@Nonnull Name name, int anzNewRows) { + AreaReference areaReference = new AreaReference(name.getRefersToFormula(), EXCEL2007); + CellReference firstCell = areaReference.getFirstCell(); + CellReference lastCell = areaReference.getLastCell(); + + String formula = String.format("%s!$%s$%d:$%s$%d", getFormulaSafeSheetName(name), + CellReference.convertNumToColString(firstCell.getCol()), firstCell.getRow() + 1, + CellReference.convertNumToColString(lastCell.getCol()), lastCell.getRow() + 1 + anzNewRows + ); + + LOG.debug("formula conversion: {} -> {}", name.getRefersToFormula(), formula); + name.setRefersToFormula(formula); + } + + @Nonnull + private static String getFormulaSafeSheetName(@Nonnull Name name) { + String sheetName = name.getSheetName(); + return CONTAINS_SPACES.matcher(sheetName).find() ? '\'' + sheetName + '\'' : sheetName; + } + + private static boolean intersects(@Nonnull AreaReference areaReference, int startRow, int endRow) { + int firstRow = areaReference.getFirstCell().getRow(); + int lastRow = areaReference.getLastCell().getRow(); + + if (firstRow < startRow && lastRow < startRow) { + return false; + } + + return !(firstRow > endRow && lastRow > endRow); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/StringColorCellDTO.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/StringColorCellDTO.java new file mode 100644 index 0000000..2ab4d12 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/StringColorCellDTO.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.poi.xssf.usermodel.XSSFColor; + +public class StringColorCellDTO { + + @Nonnull + private String value; + + @Nullable + private XSSFColor color; + + public StringColorCellDTO(@Nonnull String value, @Nullable XSSFColor color) { + this.value = value; + this.color = color; + } + + @Nonnull + public String getValue() { + return value; + } + + public void setValue(@Nonnull String value) { + this.value = value; + } + + @Nullable + public XSSFColor getColor() { + return color; + } + + public void setColor(@Nullable XSSFColor color) { + this.color = color; + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/Converter.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/Converter.java new file mode 100644 index 0000000..bef2e20 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/Converter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.converters; + +import java.io.Serializable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import ch.dvbern.oss.lib.excelmerger.ExcelMergeRuntimeException; +import org.apache.poi.ss.usermodel.Cell; + +@FunctionalInterface +public interface Converter extends Serializable { + + default void setCellValue(@Nonnull Cell cell, @Nonnull String pattern, @Nullable Object o) { + try { + //noinspection unchecked + setCellValueImpl(cell, pattern, (V) o); + } catch (RuntimeException rte) { + // Dient nur zum Debugging, damit der Entwickler an row und column rankommt + String format = "Error converting data on cell %d/%d with pattern %s on object %s"; + String msg = String.format(format, cell.getRowIndex(), cell.getColumnIndex(), pattern, o); + throw new ExcelMergeRuntimeException(msg, rte); // NOPMD.PreserveStackTrace + } + } + + void setCellValueImpl(@Nonnull Cell cell, @Nonnull String pattern, @Nullable V o); +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ConverterUtil.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ConverterUtil.java new file mode 100644 index 0000000..b0298b5 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ConverterUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger.converters; + +import java.awt.Color; +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.poi.ss.usermodel.Cell; + +public final class ConverterUtil { + + static final int SCALE = 6; + static final BigDecimal BD_HUNDRED = BigDecimal.valueOf(100); + static final Color GAINSBORO = new Color(220, 220, 220); + + public static final DateTimeFormatter DEFAULT_DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy"); + public static final DateTimeFormatter DATE_WITH_DAY_FORMATTER = DateTimeFormatter.ofPattern("EE, dd.MM.yyyy"); + public static final DateTimeFormatter DEFAULT_DATETIME_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); + public static final String BOOLEAN_VALUE = "X"; + public static final String EMPTY_STRING = ""; + + public static void writerNumber( + @Nonnull Cell cell, + @Nonnull String pattern, + @Nullable Number number, + boolean asInteger) { + + if (pattern.equals(cell.getStringCellValue())) { + if (number == null) { + cell.setCellValue(EMPTY_STRING); + return; + } + + if (asInteger) { + cell.setCellValue(number.longValue()); + return; + } + + cell.setCellValue(number.doubleValue()); + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, String.valueOf(number))); + } + } + + public static void writeLocalDate( + @Nonnull Cell cell, + @Nonnull String pattern, + @Nullable LocalDate dateVal, + @Nonnull DateTimeFormatter formatter) { + + if (dateVal == null) { + if (pattern.equals(cell.getStringCellValue())) { + cell.setCellValue(EMPTY_STRING); + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, EMPTY_STRING)); + } + + return; + } + + Instant instant = dateVal.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(); + Date date = Date.from(instant); + if (pattern.equals(cell.getStringCellValue())) { + cell.setCellValue(date); + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, dateVal.format(formatter))); + } + } + + private ConverterUtil() { + // utility + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ParametrisedConverter.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ParametrisedConverter.java new file mode 100644 index 0000000..a9aea07 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/ParametrisedConverter.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.converters; + +import java.util.function.Function; + +@FunctionalInterface +public interface ParametrisedConverter extends Function> { + +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/StandardConverters.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/StandardConverters.java new file mode 100644 index 0000000..39ca373 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/converters/StandardConverters.java @@ -0,0 +1,179 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger.converters; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Objects; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import ch.dvbern.oss.lib.excelmerger.StringColorCellDTO; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.BD_HUNDRED; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.BOOLEAN_VALUE; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.DEFAULT_DATETIME_FORMAT; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.DEFAULT_DATE_FORMAT; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.EMPTY_STRING; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.SCALE; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.writeLocalDate; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.writerNumber; + +public final class StandardConverters { + + public static final Converter STRING_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable String value) -> { + String stringVal = value == null ? EMPTY_STRING : value; + cell.setCellValue(cell.getStringCellValue().replace(pattern, stringVal)); + }; + + public static final Converter STRING_COLORED_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable StringColorCellDTO dto) -> { + if (dto != null) { + String stringVal = dto.getValue(); + cell.setCellValue(cell.getStringCellValue().replace(pattern, stringVal)); + + if (dto.getColor() != null) { + XSSFWorkbook wb = (XSSFWorkbook) cell.getSheet().getWorkbook(); + XSSFCellStyle newCellStyle = wb.getStylesSource().createCellStyle(); + newCellStyle.cloneStyleFrom(cell.getCellStyle()); + newCellStyle.setFillForegroundColor(dto.getColor()); + newCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + cell.setCellStyle(newCellStyle); + } + } + }; + + public static final ParametrisedConverter LOCAL_DATE_CONVERTER = + formatter -> (@Nonnull Cell cell, @Nonnull String pattern, @Nullable LocalDate value) -> + writeLocalDate(cell, pattern, value, formatter); + + public static final Converter DATE_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable LocalDate value) -> + LOCAL_DATE_CONVERTER.apply(DEFAULT_DATE_FORMAT).setCellValueImpl(cell, pattern, value); + + public static final ParametrisedConverter LOCAL_DATETIME_CONVERTER = + formatter -> (@Nonnull Cell cell, @Nonnull String pattern, @Nullable LocalDateTime dateVal) -> { + if (pattern.equals(cell.getStringCellValue())) { + if (dateVal == null) { + // schade... bei setCellValue(Date) darf kein null uebergeben werden + cell.setCellValue(EMPTY_STRING); + } else { + // ganze Zelle ist Datum -> die Zelle auch als Datum setzen + Date date = Date.from(dateVal.atZone(ZoneId.systemDefault()).toInstant()); + cell.setCellValue(date); + } + } else { + // nur ein Ausschnitt + if (dateVal == null) { + cell.setCellValue(cell.getStringCellValue().replace(pattern, EMPTY_STRING)); + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, dateVal.format(formatter))); + } + } + }; + + public static final Converter DATETIME_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable LocalDateTime value) -> + LOCAL_DATETIME_CONVERTER.apply(DEFAULT_DATETIME_FORMAT).setCellValueImpl(cell, pattern, value); + + public static final Converter INTEGER_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable Integer value) -> + writerNumber(cell, pattern, value, true); + + public static final Converter LONG_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable Long value) -> + writerNumber(cell, pattern, value, true); + + public static final Converter BIGDECIMAL_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable BigDecimal value) -> + writerNumber(cell, pattern, value, false); + + public static final Converter PERCENT_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable BigDecimal value) -> { + if (pattern.equals(cell.getStringCellValue())) { + if (value != null) { + double doubleValue = value.divide(BD_HUNDRED, SCALE, BigDecimal.ROUND_HALF_UP).doubleValue(); + cell.setCellValue(doubleValue); + } else { + cell.setCellValue(EMPTY_STRING); + } + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, value + "%")); + } + }; + + /** + * Converts NULL to false + */ + public static final Converter BOOLEAN_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable Boolean value) -> { + Boolean boolVal = value == null ? Boolean.FALSE : value; + + if (pattern.equals(cell.getStringCellValue())) { + cell.setCellValue(boolVal); + } else { + cell.setCellValue(cell.getStringCellValue().replace(pattern, String.valueOf(boolVal))); + } + }; + + /** + * Writes X when TRUE, otherwise writes empty string + */ + public static final Converter BOOLEAN_X_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable Boolean value) -> { + String stringVal = Objects.equals(Boolean.TRUE, value) ? BOOLEAN_VALUE : EMPTY_STRING; + + STRING_CONVERTER.setCellValueImpl(cell, pattern, stringVal); + }; + + /** + * Colors the cell using the given color when TRUE, otherwise does nothing. + */ + public static final ParametrisedConverter CELL_COLORING_CONVERTER = + color -> (@Nonnull Cell cell, @Nonnull String pattern, @Nullable Boolean value) -> { + + cell.setCellValue(cell.getStringCellValue().replace(pattern, EMPTY_STRING)); + if (Objects.equals(value, Boolean.TRUE)) { + XSSFWorkbook wb = (XSSFWorkbook) cell.getSheet().getWorkbook(); + XSSFCellStyle newCellStyle = wb.getStylesSource().createCellStyle(); + newCellStyle.cloneStyleFrom(cell.getCellStyle()); + newCellStyle.setFillForegroundColor(color); + newCellStyle.setFillBackgroundColor(color); + newCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + cell.setCellStyle(newCellStyle); + } + }; + + public static final Converter DO_NOTHING_CONVERTER = + (@Nonnull Cell cell, @Nonnull String pattern, @Nullable String value) -> { + // nop + }; + + private StandardConverters() { + // utility class + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/DefaultMergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/DefaultMergeField.java new file mode 100644 index 0000000..0f8c047 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/DefaultMergeField.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.converters.Converter; +import com.google.common.base.MoreObjects; + +public class DefaultMergeField implements MergeField { + + private static final long serialVersionUID = -4600129143718111847L; + + @Nonnull + private final String key; + + @Nonnull + private final Type type; + + @Nonnull + private final Converter converter; + + public DefaultMergeField( + @Nonnull String key, + @Nonnull Type type, + @Nonnull Converter converter) { + + this.key = key; + this.type = type; + this.converter = converter; + } + + @Nonnull + @Override + public String getKey() { + return key; + } + + @Nonnull + @Override + public Type getType() { + return type; + } + + @Nonnull + @Override + public Converter getConverter() { + return converter; + } + + @Override + @Nonnull + public String toString() { + return MoreObjects.toStringHelper(this) + .add("key", key) + .add("type", type) + .toString(); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeField.java new file mode 100644 index 0000000..1644bb0 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeField.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import java.io.Serializable; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.converters.Converter; + +public interface MergeField extends Serializable { + @Nonnull + String getKey(); + + @Nonnull + Type getType(); + + @Nonnull + Converter getConverter(); + + enum Type { + /** + * Ein einfacher Platzhalter + */ + SIMPLE(true, false, false), + /** + * Ein Platzhalter in den Ueberschriften, der mehrere Spalten hat (z.B. Ueberschrift mit den Kita-Namen) + */ + REPEAT_COL(true, true, true), + REPEAT_VAL(true, true, false), + /** + * Kennzeichnet eine Excel-Row, die wiederholt werden soll + */ + REPEAT_ROW(false, false, false), + /** + * Fügt einen Seitenumbruch ein + */ + PAGE_BREAK(true, true, true); + + private final boolean mergeValue; + private final boolean consumesValue; + private final boolean hideColumOnEmpty; + + Type(boolean mergeValue, boolean consumesValue, boolean hideColumOnEmpty) { + this.mergeValue = mergeValue; + this.consumesValue = consumesValue; + this.hideColumOnEmpty = hideColumOnEmpty; + } + + public boolean doMergeValue() { + return mergeValue; + } + + public boolean doConsumeValue() { + return consumesValue; + } + + public boolean doHideColumnOnEmpty() { + return hideColumOnEmpty; + } + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeFieldProvider.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeFieldProvider.java new file mode 100644 index 0000000..61319ca --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/MergeFieldProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; + +public interface MergeFieldProvider { + + @Nonnull + static List> toMergeFields(@Nonnull MergeFieldProvider[] values) { + return Arrays.stream(values) + .map(v -> (MergeField) v.getMergeField()) + .collect(Collectors.toList()); + } + + @Nonnull + MergeField getMergeField(); +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatColMergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatColMergeField.java new file mode 100644 index 0000000..83cd0df --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatColMergeField.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.converters.Converter; + +public class RepeatColMergeField extends DefaultMergeField { + + private static final long serialVersionUID = -3849138938343690817L; + + public RepeatColMergeField(@Nonnull String key, @Nonnull Converter converter) { + super(key, Type.REPEAT_COL, converter); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatRowMergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatRowMergeField.java new file mode 100644 index 0000000..611dac7 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatRowMergeField.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import javax.annotation.Nonnull; + +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.DO_NOTHING_CONVERTER; + +public class RepeatRowMergeField extends DefaultMergeField { + + private static final long serialVersionUID = -3896240820489518571L; + + public RepeatRowMergeField(@Nonnull String key) { + super(key, Type.REPEAT_ROW, DO_NOTHING_CONVERTER); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatValMergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatValMergeField.java new file mode 100644 index 0000000..1498215 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/RepeatValMergeField.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.converters.Converter; + +public class RepeatValMergeField extends DefaultMergeField { + + private static final long serialVersionUID = 1492409338523157658L; + + public RepeatValMergeField(@Nonnull String key, @Nonnull Converter converter) { + super(key, Type.REPEAT_VAL, converter); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/SimpleMergeField.java b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/SimpleMergeField.java new file mode 100644 index 0000000..25dcb10 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/main/java/ch/dvbern/oss/lib/excelmerger/mergefields/SimpleMergeField.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger.mergefields; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.converters.Converter; + +public class SimpleMergeField extends DefaultMergeField { + + private static final long serialVersionUID = -2233576920841715572L; + + public SimpleMergeField(@Nonnull String key, @Nonnull Converter converter) { + super(key, Type.SIMPLE, converter); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/CopyRowTest.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/CopyRowTest.java new file mode 100644 index 0000000..68658e0 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/CopyRowTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import ch.dvbern.oss.lib.excelmerger.converters.StandardConverters; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatRowMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.SimpleMergeField; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CopyRowTest { + + @Test + public void testMergedRegionsAreCopied() throws Exception { + String filename = "copyRowWithMergedRegion.xlsx"; + Workbook wb = ExcelMergerTestUtil.GET_WORKBOOK.apply(ExcelMergerTestUtil.BASE + filename); + + RepeatRowMergeField repeatRowField = new RepeatRowMergeField("repeatRow"); + SimpleMergeField mergedColumns = + new SimpleMergeField<>("mergedColumns", StandardConverters.STRING_CONVERTER); + + RepeatRowMergeField repeatRow2Field = new RepeatRowMergeField("repeatRow2"); + SimpleMergeField mergedColumns2 = + new SimpleMergeField<>("mergedColumns2", StandardConverters.STRING_CONVERTER); + + ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + + int numberOfMergedColumns = 10; + for (int i = 0; i < numberOfMergedColumns; i++) { + ExcelMergerDTO repeatRow = excelMergerDTO.createGroup(repeatRowField); + repeatRow.addValue(mergedColumns, "foo " + (i + 1)); + } + + int numberOfMergedColumns2 = 7; + for (int i = 0; i < numberOfMergedColumns2; i++) { + ExcelMergerDTO group = excelMergerDTO.createGroup(repeatRow2Field); + group.addValue(mergedColumns2, "bar " + (i + 1)); + } + + Sheet sheet = wb.getSheetAt(0); + + List> fields = + Arrays.asList(repeatRowField, mergedColumns, repeatRow2Field, mergedColumns2); + + ExcelMerger.mergeData(sheet, fields, excelMergerDTO); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, filename); + + assertEquals(numberOfMergedColumns + numberOfMergedColumns2, sheet.getNumMergedRegions()); + + for (int i = 0; i < numberOfMergedColumns; i++) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(i, i, 0, 2); + assertTrue(sheet.getMergedRegions().contains(cellRangeAddress)); + } + + for (int i = 0; i < numberOfMergedColumns2; i++) { + int row = numberOfMergedColumns + 1 + i; + CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, 0, 1); + assertTrue(sheet.getMergedRegions().contains(cellRangeAddress)); + } + } + + @Test + public void testMergedRegionsAreCopiedWithGroups() throws Exception { + String filename = "copyGroupWithMergedRegion.xlsx"; + Workbook wb = ExcelMergerTestUtil.GET_WORKBOOK.apply(ExcelMergerTestUtil.BASE + filename); + + RepeatRowMergeField repeatRowField = new RepeatRowMergeField("repeatGroup"); + SimpleMergeField mergedColumn = + new SimpleMergeField<>("mergedColumn", StandardConverters.STRING_CONVERTER); + + SimpleMergeField mergedRowAndColumn = + new SimpleMergeField<>("mergedRowAndColumn", StandardConverters.STRING_CONVERTER); + + ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + + int numberOfCopies = 10; + for (int i = 0; i < numberOfCopies; i++) { + ExcelMergerDTO repeatRow = excelMergerDTO.createGroup(repeatRowField); + repeatRow.addValue(mergedColumn, "foo " + (i + 1)); + repeatRow.addValue(mergedRowAndColumn, "bar " + (i + 1)); + } + + Sheet sheet = wb.getSheetAt(0); + ExcelMerger.mergeData(sheet, Arrays.asList(repeatRowField, mergedColumn, mergedRowAndColumn), excelMergerDTO); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, filename); + + assertEquals(numberOfCopies * 2L, sheet.getNumMergedRegions()); + for (int i = 0; i < numberOfCopies; i++) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(i, i, 0, 2); + sheet.getMergedRegions().contains(cellRangeAddress); + } + } + + @Test + public void testNamedRegion() throws Exception { + String filename = "copyRowWithNamedRegion.xlsx"; + Workbook wb = ExcelMergerTestUtil.GET_WORKBOOK.apply(ExcelMergerTestUtil.BASE + filename); + + RepeatRowMergeField repeatRowField = new RepeatRowMergeField("repeatRow"); + SimpleMergeField mergedColumns = + new SimpleMergeField<>("value", StandardConverters.LONG_CONVERTER); + + RepeatRowMergeField repeatRow2Field = new RepeatRowMergeField("repeatRow2"); + SimpleMergeField mergedColumns2 = + new SimpleMergeField<>("value2", StandardConverters.LONG_CONVERTER); + + ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + + int numberOfRows1 = 10; + for (int i = 0; i < numberOfRows1; i++) { + ExcelMergerDTO repeatRow = excelMergerDTO.createGroup(repeatRowField); + repeatRow.addValue(mergedColumns, Long.valueOf(i)); + } + + int numberOfRows2 = 7; + for (int i = 0; i < numberOfRows2; i++) { + ExcelMergerDTO group = excelMergerDTO.createGroup(repeatRow2Field); + group.addValue(mergedColumns2, Long.valueOf(i)); + } + + Sheet sheet = wb.getSheetAt(0); + + List> fields = + Arrays.asList(repeatRowField, mergedColumns, repeatRow2Field, mergedColumns2); + + ExcelMerger.mergeData(sheet, fields, excelMergerDTO); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, filename); + + int totalRow = 3 + numberOfRows1 + numberOfRows2; + int total1 = Double.valueOf(sheet.getRow(totalRow).getCell(1).getNumericCellValue()).intValue(); + int total2 = Double.valueOf(sheet.getRow(totalRow).getCell(3).getNumericCellValue()).intValue(); + assertEquals(45, total1); + assertEquals(21, total2); + } + + @Test + public void testShiftDataValidations() throws Exception { + String filename = "shiftDataValidations.xlsx"; + Workbook wb = ExcelMergerTestUtil.GET_WORKBOOK.apply(ExcelMergerTestUtil.BASE + filename); + RepeatRowMergeField repeatRowField = new RepeatRowMergeField("repeatRow"); + + ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + excelMergerDTO.createGroup(repeatRowField); + excelMergerDTO.createGroup(repeatRowField); + excelMergerDTO.createGroup(repeatRowField); + + Sheet sheet = wb.getSheetAt(0); + + assertEquals(1, sheet.getDataValidations().size()); + CellRangeAddress addressBefore = sheet.getDataValidations().get(0).getRegions().getCellRangeAddress(0); + + ExcelMerger.mergeData(sheet, Collections.singletonList(repeatRowField), excelMergerDTO); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, filename); + + assertEquals(1, sheet.getDataValidations().size()); + CellRangeAddress addressAfter = sheet.getDataValidations().get(0).getRegions().getCellRangeAddress(0); + + // two rows are added on top of the data validations + assertEquals(addressBefore.getFirstRow() + 2, addressAfter.getFirstRow()); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerMergeGroupTest.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerMergeGroupTest.java new file mode 100644 index 0000000..7234c5c --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerMergeGroupTest.java @@ -0,0 +1,329 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeFieldProvider; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.Test; + +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.BELEGUNGSPLAN; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.GET_WORKBOOK; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.WARTELISTE; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.getVal; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.writeWorkbookToFile; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@SuppressWarnings("JUnitTestMethodWithNoAssertions") +public class ExcelMergerMergeGroupTest { + + @Test + public void testMergeSingleGroup_shouldHandleZeroRows() throws Exception { + final int numRowsTest = 0; + Tester newMerger = new Tester(new SimpleGroup(numRowsTest, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + @Test + public void testMergeSingleGroup_shouldHandleSingleRow() throws Exception { + final int numRowsTest = 1; + Tester newMerger = new Tester(new SimpleGroup(numRowsTest, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + @Test + public void testMergeGroupWithMultipleRows_shouldHandleZeroGroups() throws Exception { + Tester newMerger = new Tester(new GroupWithMultipleRows(0, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + @Test + public void testMergeGroupWithMultipleRows_shouldHandleSingleGroup() throws Exception { + Tester newMerger = new Tester(new GroupWithMultipleRows(1, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + @Test + public void testMergeGroupWithMultipleRows_shouldHandleMultipleGroups() throws Exception { + Tester newMerger = new Tester(new GroupWithMultipleRows(3, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + @Test + public void testNestedGroups() throws Exception { + Tester newMerger = new Tester(new NestedGroups(1, 2, ExcelMerger::mergeSubGroup)); + newMerger.test("improved"); + } + + private static class SimpleGroup extends ContextBuilder { + + public SimpleGroup(int numRowsTest, @Nonnull ExcelMerger.GroupMerger merger) { + super(GET_WORKBOOK.apply(WARTELISTE), merger); + setNumRowsBefore(5); + setNumRowsAfter(2); + setNumRowsTest(numRowsTest); + + for (int i = 0; i < numRowsTest; i++) { + addKind(getExcelMergerDTO()); + } + + MergeFieldProvider.toMergeFields(MergeFieldWarteliste.values()) + .forEach(field -> getFieldMap().put(field.getKey(), field)); + } + + @Override + protected void validate() { + int numRowsTotal = getNumRowsBefore() + getNumRowsTest() + getNumRowsAfter(); + if (getNumRowsTest() == 0) { + // Weil die bisherige Implementation eine leere Zeile erzeugt, wenn es keine Daten gibt + numRowsTotal++; + } + + assertEquals("Should not add too many rows", numRowsTotal, getSheet().getLastRowNum() + 1); + for (int i = 1; i <= getNumRowsTest(); i++) { + assertEquals("Gates", getVal(getSheet(), getNumRowsBefore() + i, "L")); + } + + assertEquals("", getVal(getSheet(), numRowsTotal - 1, "A")); + assertEquals("TOTAL", getVal(getSheet(), numRowsTotal, "A")); + } + + private void addKind(@Nonnull ExcelMergerDTO excelData) { + ExcelMergerDTO kind = excelData.createGroup(MergeFieldWarteliste.REPEAT_KIND.getMergeField()); + kind.addValue(MergeFieldWarteliste.NAME, "Gates"); + kind.addValue(MergeFieldWarteliste.VORNAME, "Bill"); + kind.addValue(MergeFieldWarteliste.GEBURTSTAG, LocalDate.now()); + kind.addValue(MergeFieldWarteliste.KITA_BESETZT, true); + kind.addValue(MergeFieldWarteliste.KITA_BESETZT, false); + kind.addValue(MergeFieldWarteliste.FIRMA, false); + kind.addValue(MergeFieldWarteliste.FIRMA, true); + kind.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MIN, BigDecimal.valueOf(0.5)); + kind.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MAX, BigDecimal.valueOf(1)); + } + } + + private static class NestedGroups extends GroupWithMultipleRows { + + private final int numNestedGroups; + + public NestedGroups(int numGroups, int numNestedGroups, @Nonnull ExcelMerger.GroupMerger merger) { + super(merger); + setNumGroups(numGroups); + this.numNestedGroups = numNestedGroups; + setNumRowsTest(6 * Math.max(numGroups, 1) + + (Math.max(numNestedGroups, 1) - 1) * numGroups); // im Belegungsplan gibt es 6 Zeilen pro Gruppe + + addData(); + } + + @Override + protected void addData() { + for (int i = 0; i < getNumGroups(); i++) { + ExcelMergerDTO group1 = getExcelMergerDTO().createGroup(MergeFieldBelegungsplan.REPEAT_GROUP); + group1.addValue(MergeFieldBelegungsplan.GRUPPEN_NAME, "Helden"); + + for (int j = 0; j < getNumNestedGroups(); j++) { + ExcelMergerDTO group1kind1 = group1.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group1kind1.addValue(MergeFieldBelegungsplan.NAME, "Tester"); + } + } + } + + public int getNumNestedGroups() { + return numNestedGroups; + } + } + + private static class GroupWithMultipleRows extends ContextBuilder { + + private int numGroups; + + public GroupWithMultipleRows(int numGroups, @Nonnull ExcelMerger.GroupMerger merger) { + this(merger); + this.numGroups = numGroups; + setNumRowsTest(6 * Math.max(numGroups, 1)); // im Belegungsplan gibt es 6 Zeilen pro Gruppe + + addData(); + } + + protected GroupWithMultipleRows(@Nonnull ExcelMerger.GroupMerger merger) { + super(GET_WORKBOOK.apply(BELEGUNGSPLAN), merger); + setNumRowsBefore(5); + setNumRowsAfter(5); + + MergeFieldProvider.toMergeFields(MergeFieldBelegungsplan.values()) + .forEach(field -> getFieldMap().put(field.getKey(), field)); + } + + protected void addData() { + for (int i = 0; i < numGroups; i++) { + ExcelMergerDTO group1 = getExcelMergerDTO().createGroup(MergeFieldBelegungsplan.REPEAT_GROUP); + group1.addValue(MergeFieldBelegungsplan.GRUPPEN_NAME, "Helden"); + } + } + + @Override + protected void validate() { + int numRowsTotal = getNumRowsBefore() + getNumRowsTest() + getNumRowsAfter(); + + assertEquals("Should not add too many rows", numRowsTotal, getSheet().getLastRowNum() + 1); + + assertEquals("Belegung", getVal(getSheet(), numRowsTotal - 7, "A")); + assertEquals("Plätze", getVal(getSheet(), numRowsTotal - 6, "A")); + assertEquals("Max Plätze", getVal(getSheet(), numRowsTotal - 5, "A")); + + assertEquals("", getVal(getSheet(), numRowsTotal - 4, "A")); + assertEquals("Belegung Kita", getVal(getSheet(), numRowsTotal - 3, "A")); + assertEquals("Plätze Kita", getVal(getSheet(), numRowsTotal - 2, "A")); + assertEquals("Max Plätze Kita", getVal(getSheet(), numRowsTotal - 1, "A")); + assertEquals("Bewilligte Plätze Kita", getVal(getSheet(), numRowsTotal, "A")); + } + + protected void setNumGroups(int numGroups) { + this.numGroups = numGroups; + } + + public int getNumGroups() { + return numGroups; + } + } + + private abstract static class ContextBuilder { + private int numRowsBefore = 5; + private int numRowsAfter = 2; + private int numRowsTest; + @Nonnull + private final Map> fieldMap = new HashMap<>(); + @Nonnull + private final Workbook workbook; + @Nonnull + private final ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + @Nonnull + private final ExcelMerger.GroupMerger merger; + + protected ContextBuilder(@Nonnull Workbook workbook, @Nonnull ExcelMerger.GroupMerger merger) { + this.workbook = workbook; + this.merger = merger; + } + + protected abstract void validate(); + + protected void setNumRowsBefore(int numRowsBefore) { + this.numRowsBefore = numRowsBefore; + } + + public int getNumRowsBefore() { + return numRowsBefore; + } + + protected void setNumRowsAfter(int numRowsAfter) { + this.numRowsAfter = numRowsAfter; + } + + public int getNumRowsAfter() { + return numRowsAfter; + } + + protected void setNumRowsTest(int numRowsTest) { + this.numRowsTest = numRowsTest; + } + + public int getNumRowsTest() { + return numRowsTest; + } + + @Nonnull + public Map> getFieldMap() { + return fieldMap; + } + + @Nonnull + public Workbook getWorkbook() { + return workbook; + } + + @Nonnull + public Sheet getSheet() { + return workbook.getSheetAt(0); + } + + @Nonnull + public ExcelMergerDTO getExcelMergerDTO() { + return excelMergerDTO; + } + + @Nonnull + public ExcelMerger.GroupMerger getMerger() { + return merger; + } + } + + private static class Tester extends Context { + + private final int numRowsTest; + @Nonnull + private final ExcelMerger.GroupMerger merger; + @Nonnull + private final ExcelMergerDTO excelMergerDTO; + @Nonnull + private final Procedure validator; + @Nonnull + private final Class builderClass; + + Tester(@Nonnull ContextBuilder builder) { + super(builder.getWorkbook(), builder.getSheet(), builder.getFieldMap(), builder.getNumRowsBefore()); + this.numRowsTest = builder.getNumRowsTest(); + this.excelMergerDTO = builder.getExcelMergerDTO(); + this.merger = builder.getMerger(); + this.validator = builder::validate; + this.builderClass = builder.getClass(); + } + + /** + * @return returns the duration of the merge + */ + @SuppressWarnings("UnusedReturnValue") + public long test(@Nonnull String name) throws Exception { + Row row = currentRow(); + Optional group = detectGroup(); + assertTrue("Group could not be found. Is numRowsBefore wrong?", group.isPresent()); + + long start = System.currentTimeMillis(); + ExcelMerger.mergeGroup(this, group.get(), excelMergerDTO, row, merger); + long duration = System.currentTimeMillis() - start; + + String fileName = String.format("perf_test_%s_%s_sheet_%s_rows_%s.xlsx", + builderClass.getSimpleName(), name, getSheet().getSheetName(), numRowsTest); + + writeWorkbookToFile(getWorkbook(), fileName); + + this.validator.execute(); + + return duration; + } + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTest.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTest.java new file mode 100644 index 0000000..e704a55 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeFieldProvider; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.Test; + +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.BELEGUNGSPLAN; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.GET_WORKBOOK; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.WARTELISTE; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.getNumVal; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.getVal; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.named; +import static ch.dvbern.oss.lib.excelmerger.ExcelMergerTestUtil.writeWorkbookToFile; +import static ch.dvbern.oss.lib.excelmerger.converters.ConverterUtil.DEFAULT_DATE_FORMAT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class ExcelMergerTest { + + @Test + public void testWarteliste() throws ExcelMergeException, IOException, InvalidFormatException { + Workbook wb = GET_WORKBOOK.apply(WARTELISTE); + LocalDate stichtag = LocalDate.now(); + + ExcelMergerDTO excelData = new ExcelMergerDTO(); + excelData.addValue(MergeFieldWarteliste.KITA_NAME, "Testing"); + excelData.addValue(MergeFieldWarteliste.DATUM_AUSWERTUNG, LocalDate.now()); + excelData.addValue(MergeFieldWarteliste.BETREUUNGSFAKTOR_DATUM, LocalDate.now()); + + excelData.addValue(MergeFieldWarteliste.REPEAT_KITA, "Kita 1"); + excelData.addValue(MergeFieldWarteliste.REPEAT_KITA, "Kita 2"); + + excelData.addValue(MergeFieldWarteliste.REPEAT_FIRMA, "Hammerschmiede"); + excelData.addValue(MergeFieldWarteliste.REPEAT_FIRMA, "Schnitzelklopfer"); + + excelData.addValue(MergeFieldWarteliste.REPEAT_MONTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_MONTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_MONTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DIENSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DIENSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DIENSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_MITTWOCH, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_MITTWOCH, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_MITTWOCH, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DONNERSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DONNERSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_DONNERSTAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_FREITAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_FREITAG, ""); + excelData.addValue(MergeFieldWarteliste.REPEAT_FREITAG, ""); + + ExcelMergerDTO kind1 = excelData.createGroup(MergeFieldWarteliste.REPEAT_KIND); + kind1.addValue(MergeFieldWarteliste.NAME, "Tester"); + kind1.addValue(MergeFieldWarteliste.VORNAME, "Thomas"); + kind1.addValue(MergeFieldWarteliste.GEBURTSTAG, LocalDate.now()); + kind1.addValue(MergeFieldWarteliste.KITA_BESETZT, true); + kind1.addValue(MergeFieldWarteliste.KITA_BESETZT, false); + kind1.addValue(MergeFieldWarteliste.FIRMA, false); + kind1.addValue(MergeFieldWarteliste.FIRMA, false); + kind1.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MIN, BigDecimal.valueOf(0.5)); + kind1.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MAX, BigDecimal.valueOf(1)); + + ExcelMergerDTO kind2 = excelData.createGroup(MergeFieldWarteliste.REPEAT_KIND); + kind2.addValue(MergeFieldWarteliste.NAME, "Lovelace"); + kind2.addValue(MergeFieldWarteliste.VORNAME, "Ada"); + kind2.addValue(MergeFieldWarteliste.GEBURTSTAG, LocalDate.now()); + kind2.addValue(MergeFieldWarteliste.KITA_BESETZT, false); + kind2.addValue(MergeFieldWarteliste.KITA_BESETZT, true); + kind2.addValue(MergeFieldWarteliste.FIRMA, true); + kind2.addValue(MergeFieldWarteliste.FIRMA, false); + kind2.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MIN, BigDecimal.valueOf(0.1)); + kind2.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MAX, BigDecimal.valueOf(0.20)); + + ExcelMergerDTO kind3 = excelData.createGroup(MergeFieldWarteliste.REPEAT_KIND); + kind3.addValue(MergeFieldWarteliste.NAME, "Schneider"); + kind3.addValue(MergeFieldWarteliste.VORNAME, "Helge"); + kind3.addValue(MergeFieldWarteliste.GEBURTSTAG, LocalDate.now()); + kind3.addValue(MergeFieldWarteliste.KITA_BESETZT, false); + kind3.addValue(MergeFieldWarteliste.KITA_BESETZT, true); + kind3.addValue(MergeFieldWarteliste.FIRMA, false); + kind3.addValue(MergeFieldWarteliste.FIRMA, true); + kind3.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MIN, BigDecimal.valueOf(0.50)); + kind3.addValue(MergeFieldWarteliste.PENSUM_WUNSCH_MAX, BigDecimal.valueOf(0.50)); + + Sheet sheet = wb.getSheet("Warteliste"); + assertNotNull(sheet); + + ExcelMerger.mergeData(sheet, MergeFieldProvider.toMergeFields(MergeFieldWarteliste.values()), excelData); + + writeWorkbookToFile(wb, "warteliste-filled.xlsx"); + + assertEquals(9, sheet.getLastRowNum()); // keine ueberfluessigen Zeilen hinzugefuegt + + // globale Felder + assertEquals("Warteliste Testing", getVal(sheet, 1, "A")); + assertEquals("Stand: " + stichtag.format(DEFAULT_DATE_FORMAT), getVal(sheet, 2, "A")); + + // REPEAT_COL + assertEquals("Kita 1", getVal(sheet, 5, "B")); + assertEquals("Kita 2", getVal(sheet, 5, "C")); + + // Kinderdaten + assertEquals("Tester", getVal(sheet, 6, "L")); + assertEquals("Thomas", getVal(sheet, 6, "M")); + assertEquals("X", getVal(sheet, 6, "B")); + assertEquals("", getVal(sheet, 6, "C")); + assertEquals("Lovelace", getVal(sheet, 7, "L")); + assertEquals("Ada", getVal(sheet, 7, "M")); + assertEquals("", getVal(sheet, 7, "B")); + assertEquals("X", getVal(sheet, 7, "C")); + assertEquals("Schneider", getVal(sheet, 8, "L")); + assertEquals("Helge", getVal(sheet, 8, "M")); + assertEquals("", getVal(sheet, 8, "B")); + assertEquals("X", getVal(sheet, 8, "C")); + + // Excel-Formel: zaehlenwenn + assertEquals(1.0, getNumVal(sheet, 10, "B"), 0.0); + assertEquals(2.0, getNumVal(sheet, 10, "C"), 0.0); + // Excel-Formel: Summe + assertEquals(1.10, getNumVal(sheet, 10, "DL"), 0.0); + assertEquals(1.70, getNumVal(sheet, 10, "DM"), 0.0); + + // ausgeblendete Spalten (REPEAT_COL) + // wir haben 2 Kitas + assertFalse(sheet.isColumnHidden(named("B"))); + assertFalse(sheet.isColumnHidden(named("C"))); + assertTrue(sheet.isColumnHidden(named("D"))); + assertTrue(sheet.isColumnHidden(named("E"))); + assertTrue(sheet.isColumnHidden(named("F"))); + assertTrue(sheet.isColumnHidden(named("G"))); + assertTrue(sheet.isColumnHidden(named("H"))); + assertTrue(sheet.isColumnHidden(named("I"))); + assertTrue(sheet.isColumnHidden(named("J"))); + assertTrue(sheet.isColumnHidden(named("K"))); + } + + @Test + public void testBelegungsplan() throws IOException, InvalidFormatException, ExcelMergeException { + Workbook wb = GET_WORKBOOK.apply(BELEGUNGSPLAN); + + LocalDate stichtag = LocalDate.now(); + + ExcelMergerDTO excelData = new ExcelMergerDTO(); + excelData.addValue(MergeFieldBelegungsplan.KITA_NAME, "Testkita"); + DateTimeFormatter kalenderwocheFormatter = DateTimeFormatter.ofPattern("'KW' w"); + excelData.addValue(MergeFieldBelegungsplan.KALENDERWOCHE, stichtag.format(kalenderwocheFormatter)); + + { + ExcelMergerDTO group1 = excelData.createGroup(MergeFieldBelegungsplan.REPEAT_GROUP); + group1.addValue(MergeFieldBelegungsplan.GRUPPEN_NAME, "Helden"); + ExcelMergerDTO group1kind1 = group1.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group1kind1.addValue(MergeFieldBelegungsplan.NAME, "Tester"); + ExcelMergerDTO group1kind2 = group1.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group1kind2.addValue(MergeFieldBelegungsplan.NAME, "Lovelace"); + } + + { + ExcelMergerDTO group2 = excelData.createGroup(MergeFieldBelegungsplan.REPEAT_GROUP); + group2.addValue(MergeFieldBelegungsplan.GRUPPEN_NAME, "Bastler"); + ExcelMergerDTO group2kind1 = group2.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group2kind1.addValue(MergeFieldBelegungsplan.NAME, "Honolulu"); + ExcelMergerDTO group2kind2 = group2.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group2kind2.addValue(MergeFieldBelegungsplan.NAME, "Baumeister"); + ExcelMergerDTO group2kind3 = group2.createGroup(MergeFieldBelegungsplan.REPEAT_KIND); + group2kind3.addValue(MergeFieldBelegungsplan.NAME, "Rembremmerdinger"); + } + + Sheet sheet = wb.getSheet("Belegungsplan"); + assertNotNull(sheet); + ExcelMerger.mergeData(sheet, MergeFieldProvider.toMergeFields(MergeFieldBelegungsplan.values()), excelData); + + writeWorkbookToFile(wb, "belegungsplan-filled.xlsx"); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTestUtil.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTestUtil.java new file mode 100644 index 0000000..b4ab40e --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/ExcelMergerTestUtil.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Function; + +import javax.annotation.Nonnull; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CreationHelper; +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.CellReference; + +public final class ExcelMergerTestUtil { + + private ExcelMergerTestUtil() { + // Util + } + + static final String BASE = "ch/dvbern/oss/lib/excelmerger/"; + static final String BELEGUNGSPLAN = BASE + "belegungsplan.xlsx"; + static final String WARTELISTE = BASE + "warteliste.xlsx"; + + @Nonnull + static final Function GET_WORKBOOK = (name) -> { + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); + + return ExcelMerger.createWorkbookFromTemplate(is); + }; + + @Nonnull + static String getVal(@Nonnull Sheet sheet, int rowName, @Nonnull String colName) { + int rowNum = rowName - 1; // poi is zero-based + int colNum = CellReference.convertColStringToIndex(colName); + Cell cell = sheet.getRow(rowNum).getCell(colNum); + return cell == null ? "" : cell.getStringCellValue(); + } + + static double getNumVal(@Nonnull Sheet sheet, int rowName, @Nonnull String colName) { + int rowNum = rowName - 1; // poi is zero-based + int colNum = CellReference.convertColStringToIndex(colName); + Cell cell = sheet.getRow(rowNum).getCell(colNum); + return cell.getNumericCellValue(); + } + + static int named(@Nonnull String columnName) { + return CellReference.convertColStringToIndex(columnName); + } + + @Nonnull + static Cell createCell(@Nonnull Workbook wb, @Nonnull String pattern) { + Sheet sheet = wb.createSheet("new sheet"); + + // Create a row and put some cells in it. Rows are 0 based. + Row row = sheet.createRow(0); + // Create a cell + Cell cell = row.createCell(0); + cell.setCellValue(pattern); + + return cell; + } + + static void setDataFormat(@Nonnull Workbook wb, @Nonnull Cell cell, @Nonnull String format) { + CellStyle cellStyle = wb.createCellStyle(); + CreationHelper createHelper = wb.getCreationHelper(); + cellStyle.setDataFormat(createHelper.createDataFormat().getFormat(format)); + cell.setCellStyle(cellStyle); + } + + static void writeWorkbookToFile(@Nonnull Workbook wb, @Nonnull String sheetName) throws IOException { + try (OutputStream out = new FileOutputStream("target/" + sheetName)) { + wb.write(out); + } + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldBelegungsplan.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldBelegungsplan.java new file mode 100644 index 0000000..a622223 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldBelegungsplan.java @@ -0,0 +1,181 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeFieldProvider; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatColMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatRowMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatValMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.SimpleMergeField; + +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.BIGDECIMAL_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.BOOLEAN_X_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.DATE_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.LONG_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.STRING_CONVERTER; + +public enum MergeFieldBelegungsplan implements MergeFieldProvider { + + KITA_NAME(new SimpleMergeField<>("kitaName", STRING_CONVERTER)), + KALENDERWOCHE(new SimpleMergeField<>("kalenderwoche", STRING_CONVERTER)), + BETREUUNGSFAKTOR_DATUM(new SimpleMergeField<>("betreuungsfaktorDatum", DATE_CONVERTER)), + + REPEAT_GROUP(new RepeatRowMergeField("repeatGroup")), + GRUPPEN_NAME(new SimpleMergeField<>("gruppenname", STRING_CONVERTER)), + + // Spalten-Repeats + REPEATMONTAG(new RepeatColMergeField<>("repeatMontag", STRING_CONVERTER)), + REPEATDIENSTAG(new RepeatColMergeField<>("repeatDienstag", STRING_CONVERTER)), + REPEATMITTWOCH(new RepeatColMergeField<>("repeatMittwoch", STRING_CONVERTER)), + REPEATDONNERSTAG(new RepeatColMergeField<>("repeatDonnerstag", STRING_CONVERTER)), + REPEATFREITAG(new RepeatColMergeField<>("repeatFreitag", STRING_CONVERTER)), + REPEATSAMSTAG(new RepeatColMergeField<>("repeatSamstag", STRING_CONVERTER)), + REPEATSONNTAG(new RepeatColMergeField<>("repeatSonntag", STRING_CONVERTER)), + + // Kind + REPEAT_KIND(new RepeatRowMergeField("repeatKind")), + + NAME(new SimpleMergeField<>("name", STRING_CONVERTER)), + VORNAME(new SimpleMergeField<>("vorname", STRING_CONVERTER)), + GESCHLECHT(new SimpleMergeField<>("geschlecht", STRING_CONVERTER)), + BETREUUNGSFAKTOR(new SimpleMergeField<>("betreuungsfaktor", BIGDECIMAL_CONVERTER)), + GEBURTSTAG(new SimpleMergeField<>("geburtstag", DATE_CONVERTER)), + GESCHWISTER(new SimpleMergeField<>("geschwister", BOOLEAN_X_CONVERTER)), + + MONTAG(new RepeatValMergeField<>("montag", STRING_CONVERTER)), + DIENSTAG(new RepeatValMergeField<>("dienstag", STRING_CONVERTER)), + MITTWOCH(new RepeatValMergeField<>("mittwoch", STRING_CONVERTER)), + DONNERSTAG(new RepeatValMergeField<>("donnerstag", STRING_CONVERTER)), + FREITAG(new RepeatValMergeField<>("freitag", STRING_CONVERTER)), + SAMSTAG(new RepeatValMergeField<>("samstag", STRING_CONVERTER)), + SONNTAG(new RepeatValMergeField<>("sonntag", STRING_CONVERTER)), + + BELEGUNG(new SimpleMergeField<>("belegung", BIGDECIMAL_CONVERTER)), + KINDERGARTEN1(new SimpleMergeField<>("kindergarten1", STRING_CONVERTER)), + KINDERGARTEN2(new SimpleMergeField<>("kindergarten2", STRING_CONVERTER)), + FIRMA(new SimpleMergeField<>("firma", STRING_CONVERTER)), + ANDEREGRUPPE(new SimpleMergeField<>("andereGruppe", STRING_CONVERTER)), + STATUS(new SimpleMergeField<>("status", STRING_CONVERTER)), + WUNSCHOFFEN(new SimpleMergeField<>("wunschOffen", BOOLEAN_X_CONVERTER)), + GUELTIGBIS(new SimpleMergeField<>("gueltigBis", DATE_CONVERTER)), + BEMERKUNG(new SimpleMergeField<>("bemerkung", STRING_CONVERTER)), + + // Gruppen-Summen + BELEGUNGGESCHWISTER(new SimpleMergeField<>("belegungGeschwister", LONG_CONVERTER)), + + PLAETZEMONTAG(new RepeatValMergeField<>("plaetzeMontag", LONG_CONVERTER)), + PLAETZEDIENSTAG(new RepeatValMergeField<>("plaetzeDienstag", LONG_CONVERTER)), + PLAETZEMITTWOCH(new RepeatValMergeField<>("plaetzeMittwoch", LONG_CONVERTER)), + PLAETZEDONNERSTAG(new RepeatValMergeField<>("plaetzeDonnerstag", LONG_CONVERTER)), + PLAETZEFREITAG(new RepeatValMergeField<>("plaetzeFreitag", LONG_CONVERTER)), + PLAETZESAMSTAG(new RepeatValMergeField<>("plaetzeSamstag", LONG_CONVERTER)), + PLAETZESONNTAG(new RepeatValMergeField<>("plaetzeSonntag", LONG_CONVERTER)), + + BELEGUNGMONTAG(new RepeatValMergeField<>("belegungMontag", BIGDECIMAL_CONVERTER)), + BELEGUNGDIENSTAG(new RepeatValMergeField<>("belegungDienstag", BIGDECIMAL_CONVERTER)), + BELEGUNGMITTWOCH(new RepeatValMergeField<>("belegungMittwoch", BIGDECIMAL_CONVERTER)), + BELEGUNGDONNERSTAG(new RepeatValMergeField<>("belegungDonnerstag", BIGDECIMAL_CONVERTER)), + BELEGUNGFREITAG(new RepeatValMergeField<>("belegungFreitag", BIGDECIMAL_CONVERTER)), + BELEGUNGSAMSTAG(new RepeatValMergeField<>("belegungSamstag", BIGDECIMAL_CONVERTER)), + BELEGUNGSONNTAG(new RepeatValMergeField<>("belegungSonntag", BIGDECIMAL_CONVERTER)), + + MAXPLAETZEMONTAG(new RepeatValMergeField<>("maxPlaetzeMontag", LONG_CONVERTER)), + MAXPLAETZEDIENSTAG(new RepeatValMergeField<>("maxPlaetzeDienstag", LONG_CONVERTER)), + MAXPLAETZEMITTWOCH(new RepeatValMergeField<>("maxPlaetzeMittwoch", LONG_CONVERTER)), + MAXPLAETZEDONNERSTAG(new RepeatValMergeField<>("maxPlaetzeDonnerstag", LONG_CONVERTER)), + MAXPLAETZEFREITAG(new RepeatValMergeField<>("maxPlaetzeFreitag", LONG_CONVERTER)), + MAXPLAETZESAMSTAG(new RepeatValMergeField<>("maxPlaetzeSamstag", LONG_CONVERTER)), + MAXPLAETZESONNTAG(new RepeatValMergeField<>("maxPlaetzeSonntag", LONG_CONVERTER)), + + BELEGUNGSUMME(new SimpleMergeField<>("belegungSumme", BIGDECIMAL_CONVERTER)), + PLAETZESUMME(new SimpleMergeField<>("plaetzeSumme", BIGDECIMAL_CONVERTER)), + MAXPLAETZESUMME(new SimpleMergeField<>("maxPlaetzeSumme", BIGDECIMAL_CONVERTER)), + BELEGUNGKINDERGARTEN1(new SimpleMergeField<>("belegungKindergarten1", LONG_CONVERTER)), + BELEGUNGKINDERGARTEN2(new SimpleMergeField<>("belegungKindergarten2", LONG_CONVERTER)), + + WUNSCHOFFENSUMME(new SimpleMergeField<>("wunschOffenSumme", LONG_CONVERTER)), + + PLAETZEBELEGUNG(new SimpleMergeField<>("plaetzeBelegung", BIGDECIMAL_CONVERTER)), + MAXPLAETZEBELEGUNG(new SimpleMergeField<>("maxPlaetzeBelegung", BIGDECIMAL_CONVERTER)), + + // Kita-Summen + BELEGUNGKITAGESCHWISTER(new SimpleMergeField<>("belegungKitaGeschwister", LONG_CONVERTER)), + + BELEGUNGKITAMONTAG(new RepeatValMergeField<>("belegungKitaMontag", BIGDECIMAL_CONVERTER)), + BELEGUNGKITADIENSTAG(new RepeatValMergeField<>("belegungKitaDienstag", BIGDECIMAL_CONVERTER)), + BELEGUNGKITAMITTWOCH(new RepeatValMergeField<>("belegungKitaMittwoch", BIGDECIMAL_CONVERTER)), + BELEGUNGKITADONNERSTAG(new RepeatValMergeField<>("belegungKitaDonnerstag", BIGDECIMAL_CONVERTER)), + BELEGUNGKITAFREITAG(new RepeatValMergeField<>("belegungKitaFreitag", BIGDECIMAL_CONVERTER)), + BELEGUNGKITASAMSTAG(new RepeatValMergeField<>("belegungKitaSamstag", BIGDECIMAL_CONVERTER)), + BELEGUNGKITASONNTAG(new RepeatValMergeField<>("belegungKitaSonntag", BIGDECIMAL_CONVERTER)), + + PLAETZEKITAMONTAG(new RepeatValMergeField<>("plaetzeKitaMontag", LONG_CONVERTER)), + PLAETZEKITADIENSTAG(new RepeatValMergeField<>("plaetzeKitaDienstag", LONG_CONVERTER)), + PLAETZEKITAMITTWOCH(new RepeatValMergeField<>("plaetzeKitaMittwoch", LONG_CONVERTER)), + PLAETZEKITADONNERSTAG(new RepeatValMergeField<>("plaetzeKitaDonnerstag", LONG_CONVERTER)), + PLAETZEKITAFREITAG(new RepeatValMergeField<>("plaetzeKitaFreitag", LONG_CONVERTER)), + PLAETZEKITASAMSTAG(new RepeatValMergeField<>("plaetzeKitaSamstag", LONG_CONVERTER)), + PLAETZEKITASONNTAG(new RepeatValMergeField<>("plaetzeKitaSonntag", LONG_CONVERTER)), + + MAXPLAETZEKITAMONTAG(new RepeatValMergeField<>("maxPlaetzeKitaMontag", LONG_CONVERTER)), + MAXPLAETZEKITADIENSTAG(new RepeatValMergeField<>("maxPlaetzeKitaDienstag", LONG_CONVERTER)), + MAXPLAETZEKITAMITTWOCH(new RepeatValMergeField<>("maxPlaetzeKitaMittwoch", LONG_CONVERTER)), + MAXPLAETZEKITADONNERSTAG(new RepeatValMergeField<>("maxPlaetzeKitaDonnerstag", LONG_CONVERTER)), + MAXPLAETZEKITAFREITAG(new RepeatValMergeField<>("maxPlaetzeKitaFreitag", LONG_CONVERTER)), + MAXPLAETZEKITASAMSTAG(new RepeatValMergeField<>("maxPlaetzeKitaSamstag", LONG_CONVERTER)), + MAXPLAETZEKITASONNTAG(new RepeatValMergeField<>("maxPlaetzeKitaSonntag", LONG_CONVERTER)), + + MAXBEWILLIGTEPLAETZEKITAMONTAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaMontag", BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITADIENSTAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaDienstag", + BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITAMITTWOCH(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaMittwoch", + BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITADONNERSTAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaDonnerstag", + BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITAFREITAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaFreitag", + BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITASAMSTAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaSamstag", + BIGDECIMAL_CONVERTER)), + MAXBEWILLIGTEPLAETZEKITASONNTAG(new RepeatValMergeField<>("maxBewilligtePlaetzeKitaSonntag", + BIGDECIMAL_CONVERTER)), + + belegungKitaBelegung(new SimpleMergeField<>("belegungKitaBelegung", BIGDECIMAL_CONVERTER)), + belegungKitaPlaetze(new SimpleMergeField<>("belegungKitaPlaetze", BIGDECIMAL_CONVERTER)), + belegungKitaMaxPlaetze(new SimpleMergeField<>("belegungKitaMaxPlaetze", BIGDECIMAL_CONVERTER)), + belegungKitaKindergarten1(new SimpleMergeField<>("belegungKitaKindergarten1", LONG_CONVERTER)), + belegungKitaKindergarten2(new SimpleMergeField<>("belegungKitaKindergarten2", LONG_CONVERTER)), + wunschOffenKitaSumme(new SimpleMergeField<>("wunschOffenKitaSumme", LONG_CONVERTER)), + plaetzeKitaBelegung(new SimpleMergeField<>("plaetzeKitaBelegung", LONG_CONVERTER)), + maxPlaetzeKitaBelegung(new SimpleMergeField<>("maxPlaetzeKitaBelegung", LONG_CONVERTER)), + bewilligtePlaetzeKitaBelegung(new SimpleMergeField<>("bewilligtePlaetzeKitaBelegung", BIGDECIMAL_CONVERTER)); + + @Nonnull + private final MergeField mergeField; + + MergeFieldBelegungsplan(@Nonnull MergeField mergeField) { + this.mergeField = mergeField; + } + + @Override + @Nonnull + public MergeField getMergeField() { + //noinspection unchecked + return (MergeField) mergeField; + } + +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldTest.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldTest.java new file mode 100644 index 0000000..bc6320a --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.SimpleMergeField; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Test; + +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.DATE_CONVERTER; +import static org.junit.Assert.assertEquals; + +public class MergeFieldTest { + + @Test + public void testLocalDateMergeField() throws Exception { + Workbook wb = new XSSFWorkbook(); + Cell cell = ExcelMergerTestUtil.createCell(wb, "{date}"); + + ExcelMergerTestUtil.setDataFormat(wb, cell, "dd.mm.yyyy"); + SimpleMergeField localDateMergeField = new SimpleMergeField<>("date", DATE_CONVERTER); + + ExcelMergerDTO excelMergerDTO = new ExcelMergerDTO(); + LocalDate localDate = LocalDate.of(2017, 9, 30); + excelMergerDTO.addValue(localDateMergeField, localDate); + + ExcelMerger.mergeData(wb.getSheetAt(0), new MergeField[] { localDateMergeField }, excelMergerDTO); + + Date expected = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); + assertEquals(expected, cell.getDateCellValue()); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, "localDateTest.xlsx"); + } + +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldWarteliste.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldWarteliste.java new file mode 100644 index 0000000..c2d92f3 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/MergeFieldWarteliste.java @@ -0,0 +1,117 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ +package ch.dvbern.oss.lib.excelmerger; + +import javax.annotation.Nonnull; + +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.MergeFieldProvider; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatColMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatRowMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.RepeatValMergeField; +import ch.dvbern.oss.lib.excelmerger.mergefields.SimpleMergeField; + +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.BIGDECIMAL_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.BOOLEAN_X_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.DATE_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.LONG_CONVERTER; +import static ch.dvbern.oss.lib.excelmerger.converters.StandardConverters.STRING_CONVERTER; + +public enum MergeFieldWarteliste implements MergeFieldProvider { + REPEAT_KIND(new RepeatRowMergeField("repeatKind")), + + KITA_NAME(new SimpleMergeField<>("kitaName", STRING_CONVERTER)), + DATUM_AUSWERTUNG(new SimpleMergeField<>("datumAuswertung", DATE_CONVERTER)), + BETREUUNGSFAKTOR_DATUM(new SimpleMergeField<>("betreuungsfaktorDatum", DATE_CONVERTER)), + + REPEAT_KITA(new RepeatColMergeField<>("repeatKita", STRING_CONVERTER)), + KITA_BESETZT(new RepeatValMergeField<>("kitaBesetzt", BOOLEAN_X_CONVERTER)), + + VORNAME(new SimpleMergeField<>("vorname", STRING_CONVERTER)), + NAME(new SimpleMergeField<>("name", STRING_CONVERTER)), + GESCHLECHT(new SimpleMergeField<>("geschlecht", STRING_CONVERTER)), + GEBURTSTAG(new SimpleMergeField<>("geburtstag", DATE_CONVERTER)), + GEBURTSTERMIN(new SimpleMergeField<>("geburtstermin", DATE_CONVERTER)), + BETREUUNGSFAKTOR(new SimpleMergeField<>("betreuungsfaktor", BIGDECIMAL_CONVERTER)), + POLITISCHE_GEMEINDE(new SimpleMergeField<>("politischeGemeinde", STRING_CONVERTER)), + GESCHWISTER(new SimpleMergeField<>("geschwister", BOOLEAN_X_CONVERTER)), + PENSUM_MIN(new SimpleMergeField<>("pensumMin", BOOLEAN_X_CONVERTER)), + PENSUM_MAX(new SimpleMergeField<>("pensumMax", BOOLEAN_X_CONVERTER)), + + REPEAT_MONTAG(new RepeatColMergeField<>("repeatMontag", STRING_CONVERTER)), + MONTAG(new RepeatValMergeField<>("montag", BOOLEAN_X_CONVERTER)), + REPEAT_DIENSTAG(new RepeatColMergeField<>("repeatDienstag", STRING_CONVERTER)), + DIENSTAG(new RepeatValMergeField<>("dienstag", BOOLEAN_X_CONVERTER)), + REPEAT_MITTWOCH(new RepeatColMergeField<>("repeatMittwoch", STRING_CONVERTER)), + MITTWOCH(new RepeatValMergeField<>("mittwoch", BOOLEAN_X_CONVERTER)), + REPEAT_DONNERSTAG(new RepeatColMergeField<>("repeatDonnerstag", STRING_CONVERTER)), + DONNERSTAG(new RepeatValMergeField<>("donnerstag", BOOLEAN_X_CONVERTER)), + REPEAT_FREITAG(new RepeatColMergeField<>("repeatFreitag", STRING_CONVERTER)), + FREITAG(new RepeatValMergeField<>("freitag", BOOLEAN_X_CONVERTER)), + REPEAT_SAMSTAG(new RepeatColMergeField<>("repeatSamstag", STRING_CONVERTER)), + SAMSTAG(new RepeatValMergeField<>("samstag", BOOLEAN_X_CONVERTER)), + REPEAT_SONNTAG(new RepeatColMergeField<>("repeatSonntag", STRING_CONVERTER)), + SONNTAG(new RepeatValMergeField<>("sonntag", BOOLEAN_X_CONVERTER)), + + ANMELDE_DATUM(new SimpleMergeField<>("anmeldedatum", DATE_CONVERTER)), + BETREUUNGSWUNSCH_AB(new SimpleMergeField<>("betreuungswunschAb", DATE_CONVERTER)), + PRIORITAET(new SimpleMergeField<>("prioritaet", LONG_CONVERTER)), + SUBVENTIONIERTER_PLATZ(new SimpleMergeField<>("subventionierterPlatz", BOOLEAN_X_CONVERTER)), + PRIVATER_PLATZ(new SimpleMergeField<>("privaterPlatz", BOOLEAN_X_CONVERTER)), + KINDERGARTEN(new SimpleMergeField<>("kindergarten", BOOLEAN_X_CONVERTER)), + + REPEAT_FIRMA(new RepeatColMergeField<>("repeatFirma", STRING_CONVERTER)), + FIRMA(new RepeatValMergeField<>("firma", BOOLEAN_X_CONVERTER)), + + REPEAT_BETREUUNGSGRUND(new RepeatColMergeField<>("repeatBetreuungsgrund", STRING_CONVERTER)), + BETREUUNGSGRUND(new RepeatValMergeField<>("betreuungsGrund", BOOLEAN_X_CONVERTER)), + + PENSUM_WUNSCH_MIN(new SimpleMergeField<>("pensumWunschMin", BIGDECIMAL_CONVERTER)), + PENSUM_WUNSCH_MAX(new SimpleMergeField<>("pensumWunschMax", BIGDECIMAL_CONVERTER)), + AKTUELLE_BELEGUNG(new SimpleMergeField<>("aktuelleBelegung", BIGDECIMAL_CONVERTER)), + + AKTUELLE_KITA(new SimpleMergeField<>("aktuelleKita", STRING_CONVERTER)), + AKTUELLE_KITAKIND(new SimpleMergeField<>("aktuelleKitaKind", STRING_CONVERTER)), + + REPEAT_KONTAKTPERSON(new RepeatColMergeField<>("repeatKontaktperson", STRING_CONVERTER)), + KONTAKTPER_SONVORNAME(new SimpleMergeField<>("kontaktpersonVorname", STRING_CONVERTER)), + KONTAKTPER_SONNAME(new SimpleMergeField<>("kontaktpersonName", STRING_CONVERTER)), + KONTAKTPER_SONSTRASSE(new SimpleMergeField<>("kontaktpersonStrasse", STRING_CONVERTER)), + KONTAKTPER_SONNR(new SimpleMergeField<>("kontaktpersonNr", STRING_CONVERTER)), + KONTAKTPER_SONPLZ(new SimpleMergeField<>("kontaktpersonPlz", STRING_CONVERTER)), + KONTAKTPER_SONORT(new SimpleMergeField<>("kontaktpersonOrt", STRING_CONVERTER)), + KONTAKTPER_SONTELEFON(new SimpleMergeField<>("kontaktpersonTelefon", STRING_CONVERTER)), + KONTAKTPER_SONEMAIL(new SimpleMergeField<>("kontaktpersonEmail", STRING_CONVERTER)), + KONTAKTPER_SONFULLNAME(new SimpleMergeField<>("kontaktpersonFullName", STRING_CONVERTER)), + BEMERKUNG(new SimpleMergeField<>("bemerkung", STRING_CONVERTER)), + + // Kitas sheet + KITAS_REPEAT_KITA(new RepeatRowMergeField("kitasRepeatKita")), + KITAS_KITA(new SimpleMergeField<>("kitasKita", STRING_CONVERTER)); + + @Nonnull + private final MergeField mergeField; + + MergeFieldWarteliste(@Nonnull MergeField mergeField) { + this.mergeField = mergeField; + } + + @Override + @Nonnull + public MergeField getMergeField() { + //noinspection unchecked + return (MergeField) mergeField; + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/Procedure.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/Procedure.java new file mode 100644 index 0000000..463a02e --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/Procedure.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +@FunctionalInterface +public interface Procedure { + void execute() throws T; +} diff --git a/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/StandardConvertersTest.java b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/StandardConvertersTest.java new file mode 100644 index 0000000..3e67d78 --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/java/ch/dvbern/oss/lib/excelmerger/StandardConvertersTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 DV Bern AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * limitations under the License. + */ + +package ch.dvbern.oss.lib.excelmerger; + +import java.math.BigDecimal; + +import ch.dvbern.oss.lib.excelmerger.converters.StandardConverters; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class StandardConvertersTest { + + @Test + public void testBigDecimalConversion() throws Exception { + Workbook wb = new XSSFWorkbook(); + + String pattern = "{percentTest}"; + Cell cell = ExcelMergerTestUtil.createCell(wb, pattern); + + BigDecimal bigDecimal = BigDecimal.valueOf(70) + .setScale(0, BigDecimal.ROUND_HALF_UP); + + StandardConverters.PERCENT_CONVERTER.setCellValue(cell, pattern, bigDecimal); + + BigDecimal expectedValue = BigDecimal.valueOf(0.7); + BigDecimal actualValue = BigDecimal.valueOf(cell.getNumericCellValue()); + assertEquals(0, expectedValue.compareTo(actualValue)); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, "percentageTest.xlsx"); + } + + @Test + public void testBigDecimalConversionWithText() throws Exception { + Workbook wb = new XSSFWorkbook(); + + String pattern = "{percentTest}"; + Cell cell = ExcelMergerTestUtil.createCell(wb, "My Percentage " + pattern); + + BigDecimal bigDecimal = BigDecimal.valueOf(70) + .setScale(0, BigDecimal.ROUND_HALF_UP); + + StandardConverters.PERCENT_CONVERTER.setCellValue(cell, pattern, bigDecimal); + + assertEquals("My Percentage 70%", cell.getStringCellValue()); + + ExcelMergerTestUtil.writeWorkbookToFile(wb, "percentageWithTextTest.xlsx"); + } +} diff --git a/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/belegungsplan.xlsx b/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/belegungsplan.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f358ace962acf228225a5b99b11c8dc0cdb6e6c8 GIT binary patch literal 16204 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 literal 0 HcmV?d00001 diff --git a/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/copyRowWithNamedRegion.xlsx b/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/copyRowWithNamedRegion.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3e6a7a2379fdb5918c5bec06327f34fa9d91adcb GIT binary patch literal 5030 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 S6A)q-5o0b0#!~vfjQ2mA3u|Bi literal 0 HcmV?d00001 diff --git a/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/shiftDataValidations.xlsx b/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/shiftDataValidations.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8c01ff2edaaf80f06fe0977f7bf6bfb3f7903b69 GIT binary patch literal 9924 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 JkoxDR{{x%+-!A|F literal 0 HcmV?d00001 diff --git a/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/warteliste.xlsx b/dvbern-lib-excelmerger-impl/src/test/resources/ch/dvbern/oss/lib/excelmerger/warteliste.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..32b670c5966000790a0faa5ec6ae8ed65de83536 GIT binary patch literal 11037 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 literal 0 HcmV?d00001 diff --git a/dvbern-lib-excelmerger-impl/src/test/resources/log4j.properties b/dvbern-lib-excelmerger-impl/src/test/resources/log4j.properties new file mode 100644 index 0000000..2759dad --- /dev/null +++ b/dvbern-lib-excelmerger-impl/src/test/resources/log4j.properties @@ -0,0 +1,23 @@ +# +# Copyright 2017 DV Bern AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# limitations under the License. +# + +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..620ce43 --- /dev/null +++ b/pom.xml @@ -0,0 +1,149 @@ + + + + + + dvbern-lib-excelmerger-impl + + 4.0.0 + + + ch.dvbern.oss.maven + parent + 1.0.3 + + + ch.dvbern.oss.lib.excelmerger + dvbern-lib-excelmerger + 1.1.2-SNAPSHOT + pom + + + 1.7.22 + 3.16 + + + DVBern ExcelMerger + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-pmd-plugin + + + org.codehaus.mojo + findbugs-maven-plugin + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + check-maven-setup + + enforce + + + true + true + + + + + + [1.8.0-40,) + + + 3.3 + + + + + + + + + + + + + + + + ch.dvbern.lib.excelmerger + excelmerger-impl + ${project.version} + + + + com.google.code.findbugs + annotations + 3.0.0 + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + commons-io + commons-io + 2.4 + + + com.google.guava + guava + 19.0.20150826 + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + dom4j + dom4j + + + + + junit + junit + 4.12 + + + + From 2d2014e478d24a648e38a77529a38d9609836ac0 Mon Sep 17 00:00:00 2001 From: Fabio Heer Date: Tue, 8 Aug 2017 17:36:03 +0200 Subject: [PATCH 2/3] Add base installation instructions --- CODE_OF_CONDUCT.md | 7 +++--- CONTRIBUTING.md | 57 ++++++++++++++++++++++++++-------------------- README.md | 44 ++++++++++++++++------------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 561c180..221f607 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -22,8 +22,7 @@ include: Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances +* The use of explicit or offensive language or imagery * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic @@ -55,7 +54,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +reported by contacting the project team at opensource@dvbern.ch. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. @@ -71,4 +70,4 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c33740..7fdc000 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## Contributing -First off, thank you for considering contributing to the library lib-excelmerger! +First off, thank you for considering contributing to the library dvbern-lib-excelmerger! ### 1. Where do I go from here? If you've noticed a bug or have a question that doesn't belong on the @@ -11,9 +11,11 @@ If not, feel free to go ahead and [make one](https://github.com/dvbern/lib-excel ### 2. Did you find a bug? -* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/dvbern/lib-excelmerger/issues). +* **Ensure the bug was not already reported** by searching on GitHub under +[Issues](https://github.com/dvbern/lib-excelmerger/issues). -* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/dvbern/lib-excelmerger/issues/new). +* If you're unable to find an open issue addressing the problem, +[open a new one](https://github.com/dvbern/lib-excelmerger/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not happening. @@ -28,23 +30,29 @@ and create a branch with a descriptive name. A good branch name would be (where issue #325 is the ticket you're working on): ```sh -git checkout -b 325-add-mynewchanges123 +git checkout develop +git checkout -b 325-add-mynewchanges ``` ### 4. Code of Conduct -By beeing part of our development team and community, please abide the rules in our [Code of Conduct](CODE_OF_CONDUCT.md)! -Further information on how to report inapropriate behaviour by others, check the Enforcements-Section in said Code Of Conduct. Thank you for beeing a nice person! - +By being part of our development team and community, please abide the rules in our + [Code of Conduct](CODE_OF_CONDUCT.md)! +Further information on how to report inapropriate behaviour by others, check the Enforcements-Section +in said Code Of Conduct. Thank you for being a nice person! + ### 5. Code Styles, Guildlines & Inspections -Since we're trying to keep the code structured, readable and standardised, please check out and implement our Code Styles & Inspections if you are using IntelliJ IDEA. - -[Code Styles](https://raw.githubusercontent.com/dvbern/codestyles/master/src!IDE-settings!IntelliJ!DVBern-Conventions-2017-05-29.xml)(Add Scheme under Project Settings > Editor > Code Style) +Since we're trying to keep the code structured, readable and standardised, please check out and implement our +Code Styles & Inspections if you are using IntelliJ IDEA. -[Inspections](https://raw.githubusercontent.com/dvbern/codestyles/master/src!IDE-settings!IntelliJ!DVBern_Inspections_2017_05_19.xml)(Add Profile under Project Settings > Editor > Inspections) +[Code Styles](https://raw.githubusercontent.com/dvbern/codestyles/master/src!IDE-settings!IntelliJ!DVBern-Conventions-2017-05-29.xml) +(Add Scheme under Project Settings > Editor > Code Style) + +[Inspections](https://raw.githubusercontent.com/dvbern/codestyles/master/src!IDE-settings!IntelliJ!DVBern_Inspections_2017_05_19.xml) +(Add Profile under Project Settings > Editor > Inspections) -### 6. Make a Pull Request +### 6. Make a pull request At this point, you should switch back to your master branch and make sure it's up to date with the master branch: @@ -58,18 +66,18 @@ git pull upstream master Then update your feature branch from your local copy of master, and push it! ```sh -git checkout 325-add-mynewchanges123 +git checkout 325-add-mynewchanges git rebase master -git push --set-upstream origin 325-add-mynewchanges123 +git push --set-upstream origin 325-add-mynewchanges ``` Finally, go to GitHub and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request) -### 7. Keeping your Pull Request updated +### 7. Keeping your pull request updated -If a maintainer asks you to "rebase" your PR, they're saying that a lot of code +If a maintainer asks you to "rebase" your pull request, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. To learn more about rebasing in Git, there are a lot of @@ -78,20 +86,19 @@ To learn more about rebasing in Git, there are a lot of but here's the suggested workflow: ```sh -git checkout 325-add-mynewchanges123 -git pull --rebase upstream master -git push --force-with-lease 325-add-mynewchanges123 +git checkout 325-add-mynewchanges +git pull --rebase upstream develop +git push --force-with-lease 325-add-mynewchanges ``` -### 8. Merging a Pull Request (maintainers only) +### 8. Merging a pull request (maintainers only) -A Pull Reques can only be merged into master by a maintainer if: +A pull request can only be merged into develop by a maintainer if: * It is passing the tests. * It has been approved by at least two maintainers. If it was a maintainer who - opened the Pull Reques, only one extra approval is needed. + opened the pull request, only one extra approval is needed. * It has no requested changes. -* It is up to date with current master. +* It is up to date with current develop. -Any maintainer is allowed to merge a Pull Reques if all of these conditions are -met. +Any maintainer is allowed to merge a pull request if all of these conditions are met. diff --git a/README.md b/README.md index 603f2d7..8e0b79a 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,25 @@ # dvbern-lib-excelmerger -This library can be used to generate Excel-Reports using templates. +This Java library can be used to generate Excel-Reports using templates. -## Getting Started - -These instructions will get you an overview on how to implement and use the dvbern-lib-excelmerger library. See further down for installing or deployment notes. - -### Prerequisites - - - -``` - -``` +The template is designed and setup in MS Excel. With this lib, predefined placeholders +are replaced with dynamic values. -### Installing +## Getting Started - +### Installation - +* Checkout the repository +* run `mvn clean install` +* add the following dependency to your project +```xml + + ch.dvbern.oss.lib.excelmerger + dvbern-lib-excelmerger-impl + (NEWEST_VERSION) + ``` - -``` - - -## Deployment - -Add additional notes about how to deploy this on a live system ## Built With @@ -36,17 +28,19 @@ Add additional notes about how to deploy this on a live system ## Contributing Guidelines -Please read [Contributing.md](CONTRIBUTING.md) for the process for submitting pull requests to us. +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the process for submitting pull requests to us. ## Code of Conduct -One healthy social atmospehere is very important to us, wherefore we rate our Code of Conduct high. For details check the file [CodeOfConduct.md](CODE_OF_CONDUCT.md) +One healthy social atmospehere is very important to us, wherefore we rate our Code of Conduct high. + For details check the file [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ## Authors * **DV Bern AG** - *Initial work* - [dvbern](https://github.com/dvbern) -See also the list of [contributors](https://github.com/dvbern/lib-excelmerger/contributors) who participated in this project. +See also the list of [contributors](https://github.com/dvbern/lib-excelmerger/contributors) + who participated in this project. ## License From 30f9cc8bc3690e113522d2bccc6e788a71d1b04c Mon Sep 17 00:00:00 2001 From: Fabio Heer Date: Tue, 8 Aug 2017 17:48:43 +0200 Subject: [PATCH 3/3] Prepare for release 2.0.0 --- dvbern-lib-excelmerger-impl/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dvbern-lib-excelmerger-impl/pom.xml b/dvbern-lib-excelmerger-impl/pom.xml index fa53db3..5c4422e 100644 --- a/dvbern-lib-excelmerger-impl/pom.xml +++ b/dvbern-lib-excelmerger-impl/pom.xml @@ -18,7 +18,7 @@ ch.dvbern.oss.lib.excelmerger dvbern-lib-excelmerger - 1.1.2-SNAPSHOT + 2.0.0 4.0.0 diff --git a/pom.xml b/pom.xml index 620ce43..9bcf5c6 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ ch.dvbern.oss.lib.excelmerger dvbern-lib-excelmerger - 1.1.2-SNAPSHOT + 2.0.0 pom