This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch v3
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/v3 by this push:
new 2a8dc8f9a2b CAUSEWAY-3825: excel exporter: allow for individual cell
coloring (v3 only)
2a8dc8f9a2b is described below
commit 2a8dc8f9a2b86b2c689fadef94d3f33873de716b
Author: Andi Huber <[email protected]>
AuthorDate: Thu Nov 7 11:53:32 2024 +0100
CAUSEWAY-3825: excel exporter: allow for individual cell coloring (v3
only)
---
.../tabular/excel/exporter/CellStyleProvider.java | 195 ++++++++++++++++++---
.../tabular/excel/exporter/ExcelCellWriter.java | 4 +-
.../tabular/excel/exporter/ExcelFileWriter.java | 43 ++++-
.../excel/exporter/TabularExcelExporter.java | 15 +-
4 files changed, 227 insertions(+), 30 deletions(-)
diff --git
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
index b564bc2516a..9a02c60c40b 100644
---
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
+++
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/CellStyleProvider.java
@@ -21,6 +21,7 @@ package org.apache.causeway.extensions.tabular.excel.exporter;
import java.awt.Color;
import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.VerticalAlignment;
@@ -28,36 +29,162 @@ import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap;
import org.apache.poi.xssf.usermodel.XSSFColor;
-import lombok.Getter;
+import org.springframework.lang.Nullable;
-import lombok.experimental.Accessors;
+import org.apache.causeway.commons.tabular.TabularModel.TabularRow;
+import
org.apache.causeway.extensions.tabular.excel.exporter.ExcelFileWriter.Options.CustomCellStyle;
-//record candidate
-@Getter @Accessors(fluent=true)
-class CellStyleProvider {
+import lombok.NonNull;
- public CellStyleProvider(final Workbook wb) {
- this.workbook = wb;
- this.primaryHeaderStyle = createPrimaryHeaderRowStyle(wb);
- this.secondaryHeaderStyle = createSecondaryHeaderRowStyle(wb);
- this.dateStyle = createDateFormatCellStyle(wb, "yyyy-mm-dd");
- this.multilineStyle = createMultilineCellStyle(wb);
+record CellStyleProvider(
+ @NonNull Workbook workbook,
+ @NonNull CellStyle primaryHeaderStyle,
+ @NonNull CellStyle secondaryHeaderStyle,
+ @NonNull CellStyle dateStyle,
+ @NonNull CellStyle multilineStyle,
+ @NonNull CellStyle[] blueStyles,
+ @NonNull CellStyle[] greenStyles,
+ @NonNull CellStyle[] indigoStyles,
+ @NonNull CellStyle[] warningStyles,
+ @NonNull CellStyle[] dangerStyles,
+ @NonNull ExcelFileWriter.Options options) {
+
+ static CellStyleProvider create(final Workbook wb, @Nullable final
ExcelFileWriter.Options options) {
+ var styleIndexList = new StyleIndexList();
+ return new CellStyleProvider(wb,
+ createPrimaryHeaderRowStyle(wb),
+ createSecondaryHeaderRowStyle(wb),
+ createDateFormatCellStyle(wb, styleIndexList, "yyyy-mm-dd"),
+ createMultilineCellStyle(wb, styleIndexList),
+ createColoredStyles(wb, styleIndexList, new Color(0x6ea8fe)), //
blue-300 (6ea8fe)
+ createColoredStyles(wb, styleIndexList, new Color(0x75b798)), //
green-300 (75b798)
+ createColoredStyles(wb, styleIndexList, new Color(0xa370f7)), //
indigo-300 (a370f7)
+ createColoredStyles(wb, styleIndexList, new Color(0xffcd39)), //
warning yellow-400 (ffcd39)
+ createColoredStyles(wb, styleIndexList, new Color(0xe35d6a)), //
danger red-400 (e35d6a)
+ options!=null
+ ? options
+ : ExcelFileWriter.Options.builder().build());
+ }
+
+ /**
+ * Applies a custom cell style to given {@link Cell}
+ * based on whether any of the cellStyleFunctions is available
+ * and if so, whether any of the cellStyleFunctions returns a non-null
CellStyle.
+ * @implNote we are doing this distinction of cases for performance
reasons,
+ * that is, minimize the number of style function calls;
+ * otherwise code could be simplified
+ */
+ void applyCustomStyle(final TabularRow tabularRow, final Iterable<Cell>
cells) {
+ switch (StylePolicy.get(options)) {
+ case NONE: return;
+ case ROW_ONLY: {
+ var customRowStyle = CustomCellStyle.nullToDefault(
+ options.rowStyleFunction().apply(tabularRow));
+ if(customRowStyle.isDefault()) return;
+ for(var cell : cells) {
+ applyCustomCellStyle(cell, customRowStyle);
+ }
+ return;
+ }
+ case CELL_ONLY: {
+ int cellIndex = 0;
+ for(var cell : cells) {
+ var customCellStyle = CustomCellStyle.nullToDefault(
+
options.cellStyleFunction().apply(tabularRow.getCell(cellIndex++)));
+ applyCustomCellStyle(cell, customCellStyle);
+ }
+ return;
+ }
+ case CELL_AND_ROW: {
+ CustomCellStyle customRowStyle = null; // only calculate if
needed
+ int cellIndex = 0;
+ for(var cell : cells) {
+ var customCellStyle = CustomCellStyle.nullToDefault(
+
options.cellStyleFunction().apply(tabularRow.getCell(cellIndex++)));
+ if(!customCellStyle.isDefault()) {
+ applyCustomCellStyle(cell, customCellStyle);
+ } else {
+ if(customRowStyle==null) {
+ customRowStyle = CustomCellStyle.nullToDefault(
+
options.rowStyleFunction().apply(tabularRow));
+ }
+ applyCustomCellStyle(cell, customRowStyle);
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ Cell applyDateStyle(final Cell cell) {
+ cell.setCellStyle(dateStyle());
+ return cell;
+ }
+
+ Cell applyMultilineStyle(final Cell cell) {
+ cell.setCellStyle(multilineStyle());
+ return cell;
+ }
+
+ private void applyCustomCellStyle(final Cell cell, final CustomCellStyle
customCellStyle) {
+ if(customCellStyle==null
+ || customCellStyle.isDefault()) return;
+
+ final int ordinal = valueKindOf(cell).ordinal();
+
+ cell.setCellStyle(
+ switch (customCellStyle) {
+ case BLUE -> blueStyles()[ordinal];
+ case GREEN -> greenStyles()[ordinal];
+ case INDIGO -> indigoStyles()[ordinal];
+ case WARNING -> warningStyles()[ordinal];
+ case DANGER -> dangerStyles()[ordinal];
+ case DEFAULT -> throw new
UnsupportedOperationException("unexpected code reach");
+ });
}
- final Workbook workbook;
- final CellStyle primaryHeaderStyle;
- final CellStyle secondaryHeaderStyle;
- final CellStyle dateStyle;
- final CellStyle multilineStyle;
+ private enum ValueKind {
+ DEFAULT,
+ DATE,
+ MULTILINE;
+ }
+ private ValueKind valueKindOf(final Cell cell) {
+ if(cell.getCellStyle() == dateStyle()) return ValueKind.DATE;
+ if(cell.getCellStyle() == multilineStyle()) return ValueKind.MULTILINE;
+ return ValueKind.DEFAULT;
+ }
+
+ /**
+ * Creates a colored variant for each {@link ValueKind}.
+ * @param styleIndexList
+ */
+ private static CellStyle[] createColoredStyles(final Workbook wb, final
StyleIndexList styleIndexList, final Color color) {
+ var valueKinds = ValueKind.values();
+ var cellStyles = new CellStyle[valueKinds.length];
+ for(var valueKind : valueKinds) {
- protected CellStyle createPrimaryHeaderRowStyle(final Workbook wb) {
+ var cellStyle = wb.createCellStyle();
+
+ switch (valueKind) {
+ case DEFAULT -> {}
+ case DATE ->
cellStyle.cloneStyleFrom(wb.getCellStyleAt(styleIndexList.dateStyleIndex));
+ case MULTILINE ->
cellStyle.cloneStyleFrom(wb.getCellStyleAt(styleIndexList.multilineStyleIndex));
+ }
+
+ cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ cellStyle.setFillForegroundColor(createColor(color));
+ cellStyles[valueKind.ordinal()] = cellStyle;
+ }
+ return cellStyles;
+ }
+
+ private static CellStyle createPrimaryHeaderRowStyle(final Workbook wb) {
var font = wb.createFont();
font.setBold(true);
var cellStyle = wb.createCellStyle();
cellStyle.setFont(font);
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
cellStyle.setFillForegroundColor(createColor(new Color(0xc0c0c0)));
- //cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
@@ -65,7 +192,7 @@ class CellStyleProvider {
return cellStyle;
}
- protected CellStyle createSecondaryHeaderRowStyle(final Workbook wb) {
+ private static CellStyle createSecondaryHeaderRowStyle(final Workbook wb) {
var font = wb.createFont();
font.setFontHeightInPoints((short) 10);
var cellStyle = wb.createCellStyle();
@@ -78,25 +205,47 @@ class CellStyleProvider {
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.NONE);
cellStyle.setBorderBottom(BorderStyle.MEDIUM);
- //cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
return cellStyle;
}
- protected CellStyle createDateFormatCellStyle(final Workbook wb, final
String dateFormat) {
+ private static CellStyle createDateFormatCellStyle(final Workbook wb,
final StyleIndexList styleIndexList, final String dateFormat) {
var cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(dateFormat));
+ styleIndexList.dateStyleIndex = cellStyle.getIndex();
return cellStyle;
}
- protected CellStyle createMultilineCellStyle(final Workbook wb) {
+ private static CellStyle createMultilineCellStyle(final Workbook wb, final
StyleIndexList styleIndexList) {
var cellStyle = wb.createCellStyle();
cellStyle.setVerticalAlignment(VerticalAlignment.TOP);
cellStyle.setWrapText(true);
+ styleIndexList.multilineStyleIndex = cellStyle.getIndex();
return cellStyle;
}
- static XSSFColor createColor(final Color color) {
+ private static XSSFColor createColor(final Color color) {
return new XSSFColor(color, new DefaultIndexedColorMap());
}
+ private static class StyleIndexList {
+ short dateStyleIndex = -1;
+ short multilineStyleIndex = -1;
+ }
+
+ private enum StylePolicy {
+ NONE,
+ CELL_ONLY,
+ ROW_ONLY,
+ CELL_AND_ROW;
+ static StylePolicy get(final ExcelFileWriter.Options options) {
+ return options.rowStyleFunction()!=null
+ ? options.cellStyleFunction()!=null
+ ? StylePolicy.CELL_AND_ROW
+ : StylePolicy.ROW_ONLY
+ : options.cellStyleFunction()!=null
+ ? StylePolicy.CELL_ONLY
+ : StylePolicy.NONE;
+ }
+ }
+
}
diff --git
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
index bc69100571d..1d01a31ab08 100644
---
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
+++
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelCellWriter.java
@@ -64,7 +64,7 @@ record ExcelCellWriter(
joinedElementsLiteral += POI_LINE_DELIMITER +
String.format("(has %d more)", overflow);
}
cell.setCellValue(joinedElementsLiteral);
- cell.setCellStyle(cellStyleProvider.multilineStyle());
+ cellStyleProvider.applyMultilineStyle(cell);
return overflow>0
? maxCellElements + 1
: tabularCell.cardinality();
@@ -171,7 +171,7 @@ record ExcelCellWriter(
private static void setCellValueForDate(final Cell cell, final Date date,
final CellStyleProvider cellStyleProvider) {
cell.setCellValue(date);
- cell.setCellStyle(cellStyleProvider.dateStyle());
+ cellStyleProvider.applyDateStyle(cell);
}
}
diff --git
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
index 1adde403b07..d275f5a910c 100644
---
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
+++
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/ExcelFileWriter.java
@@ -21,6 +21,7 @@ package org.apache.causeway.extensions.tabular.excel.exporter;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
+import java.util.function.Function;
import java.util.stream.IntStream;
import org.apache.poi.ss.usermodel.Cell;
@@ -38,14 +39,48 @@ import org.apache.causeway.commons.internal.base._Reduction;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.io.DataSource;
import org.apache.causeway.commons.tabular.TabularModel;
+import org.apache.causeway.commons.tabular.TabularModel.TabularCell;
+import org.apache.causeway.commons.tabular.TabularModel.TabularRow;
+import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
/**
* Utility to write a {@link TabularModel} to file.
*/
-public record ExcelFileWriter() {
+public record ExcelFileWriter(@Nullable Options options) {
+
+ @Builder
+ public record Options(
+ /**
+ * Custom style for individual cell based on cell value. Overrules
rowStyleFunction if provided.
+ * <p>
+ * {@link Function} may return {@code null}.
+ */
+ @Nullable Function<TabularCell, CustomCellStyle> cellStyleFunction,
+ /**
+ * Custom style for entire row based on row data. Overruled by
cellStyleFunction if provided.
+ * {@link Function} may return {@code null}.<br>
+ */
+ @Nullable Function<TabularRow, CustomCellStyle> rowStyleFunction) {
+
+ public enum CustomCellStyle {
+ DEFAULT,
+ BLUE,
+ GREEN,
+ INDIGO,
+ WARNING,
+ DANGER;
+ public boolean isDefault() { return this ==
CustomCellStyle.DEFAULT; }
+ static CustomCellStyle nullToDefault(@Nullable final
CustomCellStyle customCellStyle) {
+ return customCellStyle!=null
+ ? customCellStyle
+ : DEFAULT;
+ }
+ }
+
+ }
@SneakyThrows
public void write(final TabularModel tabular, final File tempFile) {
@@ -90,7 +125,7 @@ public record ExcelFileWriter() {
var cellWriter = new ExcelCellWriter(5);
var sheet = wb.createSheet(sheetName);
- var cellStyleProvider = new CellStyleProvider(wb);
+ var cellStyleProvider = CellStyleProvider.create(wb, options);
var rowFactory = new RowFactory(sheet);
var dataColumns = tabularSheet.columns();
@@ -128,13 +163,15 @@ public record ExcelFileWriter() {
maxLinesInRow = _Reduction.of(1, Math::max); // row auto-size
calculation
for(var column : dataColumns) {
final Cell cell = row.createCell((short) i++);
+ final TabularCell tabularCell = dataRow.getCell(column);
final int linesWritten = cellWriter.setCellValue(
column,
- dataRow.getCell(column),
+ tabularCell,
cell,
cellStyleProvider);
maxLinesInRow.accept(linesWritten);
}
+ cellStyleProvider.applyCustomStyle(dataRow, row);
autoSizeRow(row, maxLinesInRow.getResult().orElse(1), null);
}
diff --git
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
index 0e38ba8f0fa..ef5819f1534 100644
---
a/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
+++
b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/exporter/TabularExcelExporter.java
@@ -20,19 +20,30 @@ package
org.apache.causeway.extensions.tabular.excel.exporter;
import java.io.File;
+import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.apache.causeway.applib.tabular.TabularExporter;
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
import org.apache.causeway.commons.tabular.TabularModel;
+import
org.apache.causeway.extensions.tabular.excel.exporter.ExcelFileWriter.Options;
@Component
public class TabularExcelExporter
implements TabularExporter {
@Override
- public void export(final TabularModel.TabularSheet tabularSheet, final
File tempFile) {
- new ExcelFileWriter().write(new TabularModel(tabularSheet), tempFile);
+ public void export(
+ final TabularModel.TabularSheet tabularSheet,
+ final File tempFile) {
+ export(tabularSheet, tempFile, (Options)null);
+ }
+
+ public void export(
+ final TabularModel.TabularSheet tabularSheet,
+ final File tempFile,
+ @Nullable final ExcelFileWriter.Options options) {
+ new ExcelFileWriter(options).write(new TabularModel(tabularSheet),
tempFile);
}
@Override