diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/ListFormulaDataValidation.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/ListFormulaDataValidation.java new file mode 100644 index 00000000..e951b38c --- /dev/null +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/ListFormulaDataValidation.java @@ -0,0 +1,127 @@ +package org.dhatim.fastexcel; + +import java.io.IOException; + +/** + * A ListDataValidation defines a DataValidation for a worksheet of type = "list" + */ +public class ListFormulaDataValidation implements DataValidation { + private final static String TYPE = "list"; + private final Range range; + private final Formula formula; + + private boolean allowBlank = true; + private boolean showDropdown = true; + private DataValidationErrorStyle errorStyle = DataValidationErrorStyle.INFORMATION; + private boolean showErrorMessage = false; + private String errorTitle; + private String error; + + /** + * Constructor + * + * @param range The Range this validation is applied to + * @param formula The Formula of this validation to retrieve the list + */ + ListFormulaDataValidation(Range range, Formula formula) { + this.range = range; + this.formula = formula; + } + + /** + * whether blank cells should pass the validation + * + * @param allowBlank whether or not to allow blank values + * @return this ListDataValidation + */ + public ListFormulaDataValidation allowBlank(boolean allowBlank) { + this.allowBlank = allowBlank; + return this; + } + + /** + * Whether Excel will show an in-cell dropdown list + * containing the validation list + * + * @param showDropdown whether or not to show the dropdown + * @return this ListDataValidation + */ + public ListFormulaDataValidation showDropdown(boolean showDropdown) { + this.showDropdown = showDropdown; + return this; + } + + /** + * The style of error alert used for this data validation. + * + * @param errorStyle The DataValidationErrorStyle for this DataValidation + * @return this ListDataValidation + */ + public ListFormulaDataValidation errorStyle(DataValidationErrorStyle errorStyle) { + this.errorStyle = errorStyle; + return this; + } + + /** + * Whether to display the error alert message when an invalid value has been entered. + * + * @param showErrorMessage whether to display the error message + * @return this ListDataValidation + */ + public ListFormulaDataValidation showErrorMessage(boolean showErrorMessage) { + this.showErrorMessage = showErrorMessage; + return this; + } + + /** + * Title bar text of error alert. + * + * @param errorTitle The error title + * @return this ListDataValidation + */ + public ListFormulaDataValidation errorTitle(String errorTitle) { + this.errorTitle = errorTitle; + return this; + } + + /** + * Message text of error alert. + * + * @param error The error message + * @return this ListDataValidation + */ + public ListFormulaDataValidation error(String error) { + this.error = error; + return this; + } + + /** + * Write this dataValidation as an XML element. + * + * @param w Output writer. + * @throws IOException If an I/O error occurs. + */ + @Override + public void write(Writer w) throws IOException { + w + .append("") + .append(formula.getExpression()) + .append(""); + } +} diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Range.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Range.java index ea15be70..9d54819c 100644 --- a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Range.java +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Range.java @@ -44,6 +44,11 @@ public class Range implements Ref { */ private final int right; + /** + * enable the folder scope when this range is added to a worksheet's named ranges + */ + private boolean folderScope = false; + /** * Constructor. Note coordinates are reordered if necessary to make sure * {@code top} <= {@code bottom} and {@code left} <= {@code right}. @@ -201,7 +206,19 @@ public ListDataValidation validateWithList(Range listRange) { return listDataValidation; } - /** + /** + * Construct a new ListDataValidation + * + * @param formula The Formula to retrieve the validation list + * @return a new list data validation object + */ + public ListFormulaDataValidation validateWithListByFormula(String formula) { + ListFormulaDataValidation listDataValidation = new ListFormulaDataValidation(this, new Formula(formula)); + worksheet.addValidation(listDataValidation); + return listDataValidation; + } + + /** * Construct a new ListDataValidation * * @param formula The custom validation formula @@ -216,13 +233,31 @@ public CustomDataValidation validateWithFormula(String formula) { /** * Specifically define this range by assigning it a name. * It will be visible in the cell range dropdown menu. - * + * * @param name string representing the name of this cell range */ public void setName(String name) { worksheet.addNamedRange(this, name); } + /** + * Check if this range has a folder scope. It is used by {@link Worksheet#addNamedRange(Range, String)}. + * + * @return {@code true} if the range has a folder scope, {@code false} if it is visible only by the worksheet contains the range + */ + public boolean isFolderScope() { + return folderScope; + } + + /** + * Set the visibility of this range + * + * @param folderScope {@code true} to allow to see the range by all worksheet + */ + public void setFolderScope(boolean folderScope) { + this.folderScope = folderScope; + } + /** * Return the set of styles used by the cells in this range. * diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java index c190eb4c..1e81b464 100644 --- a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java @@ -291,14 +291,14 @@ private boolean hasComments() { private void writeWorkbookFile() throws IOException { writeFile("xl/workbook.xml", w -> { w.append("" + - "" + - "" + - "" + - "" + - "" + - ""); + "" + + "" + + "" + + "" + + "" + + ""); for (Worksheet ws : worksheets) { writeWorkbookSheet(w, ws); @@ -312,12 +312,12 @@ private void writeWorkbookFile() throws IOException { for (Worksheet ws : worksheets) { int worksheetIndex = getIndex(ws) - 1; List repeatingColsAndRows = Stream.of(ws.getRepeatingCols(), ws.getRepeatingRows()) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + .filter(Objects::nonNull) + .collect(Collectors.toList()); if (!repeatingColsAndRows.isEmpty()) { w.append(""); } } w.append(""); @@ -366,7 +370,7 @@ private void writeWorkbookFile() throws IOException { */ private void writeWorkbookSheet(Writer w, Worksheet ws) throws IOException { w.append(" { + Worksheet ws = wb.newWorksheet(mainWorksheet); + + // add list of values + Worksheet listWs = wb.newWorksheet(worksheetWithListValues); + listWs.value(0, 0, "val1"); + listWs.value(1, 0, "val2"); + + // hidden worksheet with values + listWs.setVisibilityState(VisibilityState.HIDDEN); + + Range listRange = listWs.range(0, 0, 1, 0); + // the folder scope to the range allows to show the "named range" if the list worksheet is hidden too + listRange.setFolderScope(true); + listWs.addNamedRange(listRange, namedRange); + + + // add cell with name of "named range" to retrieve + ws.value(0, 0, "VALUES"); + + String formula = "INDIRECT($A$1)"; + + ListFormulaDataValidation listFormulaDataValidation = ws.range(0, 1, 100, 1).validateWithListByFormula(formula); + listFormulaDataValidation + .allowBlank(false) + .error(errMsg) + .errorTitle(errTitle) + .errorStyle(DataValidationErrorStyle.WARNING) + .showErrorMessage(true); + }); + + // Check generated workbook with Apache POI + XSSFWorkbook xwb = new XSSFWorkbook(new ByteArrayInputStream(data)); + assertThat(xwb.getNumberOfSheets()).isEqualTo(2); + + XSSFSheet xwsValues = xwb.getSheet(worksheetWithListValues); + // check visibility of sheet contains value list + assertThat(xwb.getSheetVisibility(xwb.getSheetIndex(xwsValues))).isEqualTo(SheetVisibility.HIDDEN); + + // check the named range and its reference + XSSFName xssfName = xwb.getName(namedRange); + xssfName.getRefersToFormula().equals("'Lists'!$A$1:$A$2"); + // check that the named range has a global scope and not a reference to the local sheet + assertThat(xssfName.getSheetIndex()).isEqualTo(-1); + + XSSFSheet xws = xwb.getSheet(mainWorksheet); + + // check number of data validation of main worksheet + assertThat(xws.getDataValidations().size()).isEqualTo(1); + + XSSFDataValidation dataValidation = xws.getDataValidations().get(0); + + assertThat(dataValidation.getEmptyCellAllowed()).isFalse(); + assertThat(dataValidation.getErrorBoxText()).isEqualTo(errMsg); + assertThat(dataValidation.getErrorBoxTitle()).isEqualTo(errTitle); + assertThat(dataValidation.getErrorStyle()).isEqualTo(ErrorStyle.WARNING); + assertThat(dataValidation.getShowErrorBox()).isTrue(); + assertThat(dataValidation.getSuppressDropDownArrow()).isTrue(); + assertThat(dataValidation.getRegions().getCellRangeAddresses().length).isEqualTo(1); + + CellRangeAddress cellRangeAddress = dataValidation.getRegions().getCellRangeAddress(0); + assertThat(cellRangeAddress.getFirstColumn()).isEqualTo(1); + assertThat(cellRangeAddress.getLastColumn()).isEqualTo(1); + assertThat(cellRangeAddress.getFirstRow()).isEqualTo(0); + assertThat(cellRangeAddress.getLastRow()).isEqualTo(100); + + + DataValidationConstraint validationConstraint = dataValidation.getValidationConstraint(); + assertThat(validationConstraint.getFormula1().toLowerCase()).isEqualToIgnoringCase("INDIRECT($A$1)"); + assertThat(validationConstraint.getValidationType()).isEqualTo(DataValidationConstraint.ValidationType.LIST); + + } + @Test void canHideSheet() throws IOException {