From 266064042045d0193a5a6247b9d3b7ae505c1599 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Tue, 25 Feb 2025 04:09:40 +0200 Subject: [PATCH 1/8] feat: Implement colors in custom formatting --- .../vaadin/addon/spreadsheet/client/Cell.java | 21 +- .../addon/spreadsheet/client/CellData.java | 5 +- .../addon/spreadsheet/client/SheetWidget.java | 29 +- .../spreadsheet/client/SpreadsheetWidget.java | 2 +- .../custom_formatting_rainbow.xlsx | Bin 0 -> 5048 bytes .../spreadsheet/test/CustomFormattingIT.java | 187 +++++ .../spreadsheet/CellValueManager.java | 6 + .../spreadsheet/CustomDataFormatter.java | 43 +- .../spreadsheet/client/CellData.java | 5 +- .../apache/poi/ss/format/CellFormatPart.java | 715 ++++++++++++++++++ 10 files changed, 990 insertions(+), 23 deletions(-) create mode 100644 vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/main/resources/test_sheets/custom_formatting_rainbow.xlsx create mode 100644 vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java create mode 100644 vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java index 4b998b0bbb7..2a37a88820d 100755 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java @@ -38,6 +38,7 @@ public class Cell { private String value; private String cellStyle = "cs0"; + private String textColor; private boolean needsMeasure; private SheetWidget sheetWidget; private boolean overflowDirty = true; @@ -63,6 +64,7 @@ public Cell(SheetWidget sheetWidget, int col, int row, CellData cellData) { needsMeasure = cellData.needsMeasure; value = cellData.value; cellStyle = cellData.cellStyle; + textColor = cellData.textColor; } updateCellValues(); updateInnerText(); @@ -72,12 +74,17 @@ public DivElement getElement() { return element; } + public String getTextColor() { + return textColor; + } + public void update(int col, int row, CellData cellData) { this.col = col; this.row = row; - cellStyle = cellData == null ? "cs0" : cellData.cellStyle; - value = cellData == null ? null : cellData.value; - needsMeasure = cellData == null ? false : cellData.needsMeasure; + this.cellStyle = cellData == null ? "cs0" : cellData.cellStyle; + this.value = cellData == null ? null : cellData.value; + this.needsMeasure = cellData == null ? false : cellData.needsMeasure; + this.textColor = cellData == null ? null : cellData.textColor; updateInnerText(); updateCellValues(); @@ -87,6 +94,7 @@ public void update(int col, int row, CellData cellData) { private void updateInnerText() { element.getStyle().setOverflow(Overflow.HIDDEN); + element.getStyle().clearColor(); if (value == null || value.isEmpty()) { element.setInnerText(""); element.getStyle().clearZIndex(); @@ -100,6 +108,10 @@ private void updateInnerText() { } } + if (textColor != null) { + element.getStyle().setColor(textColor); + } + appendOverlayElements(); } @@ -221,12 +233,13 @@ public String getValue() { return value; } - public void setValue(String value, String cellStyle, boolean needsMeasure) { + public void setValue(String value, String cellStyle, String textColor, boolean needsMeasure) { if (!this.cellStyle.equals(cellStyle)) { this.cellStyle = cellStyle; updateClassName(); } this.needsMeasure = needsMeasure; + this.textColor = textColor; setValue(value); } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/CellData.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/CellData.java index 424f702fdbd..6ccd5c9066d 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/CellData.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/CellData.java @@ -22,6 +22,7 @@ public class CellData implements Serializable { public String formulaValue; public String originalValue; public String cellStyle = "cs0"; + public String textColor; public boolean locked = false; public boolean needsMeasure; public boolean isPercentage; @@ -56,7 +57,7 @@ public boolean equals(Object obj) { @Override public String toString() { return new StringBuilder().append("r").append(row).append("c") - .append(col).append(cellStyle).append("|").append(value) - .toString(); + .append(col).append(cellStyle).append("tc").append(textColor) + .append("|").append(value).toString(); } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java index b479f378e19..4cee2ccf02e 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java @@ -3139,7 +3139,14 @@ else if (vScrollDiff < 0) { final ArrayList tempCols = new ArrayList(); for (Iterator cells = row.iterator(); cells.hasNext();) { Cell cell = cells.next(); + if (cell == null) { + // Cell can apparently be null here; scrolling will fail + // unless this is checked for. + continue; + } + int cIndex = cell.getCol(); + // scroll right if (hScrollDiff > 0) { // move cells from left to right @@ -3562,11 +3569,13 @@ public void addMergedRegion(MergedRegion region) { MergedCell mergedCell = new MergedCell(this, region.col1, region.row1); String cellStyle = "cs0"; Cell cell = getCell(region.col1, region.row1); + String textColor = null; if (cell != null) { cellStyle = cell.getCellStyle(); + textColor = cell.getTextColor(); } mergedCell.setValue(getCellValue(region.col1, region.row1), cellStyle, - false); + textColor, false); DivElement element = mergedCell.getElement(); element.addClassName(MERGED_CELL_CLASSNAME); updateMergedRegionRegionSize(region, mergedCell); @@ -3724,7 +3733,7 @@ public void removeMergedRegion(MergedRegion region, int ruleIndex) { Cell originalCell = getCell(region.col1, region.row1); if (originalCell != null) { originalCell.setValue(mCell.getValue(), mCell.getCellStyle(), - false); + originalCell.getTextColor(),false); } mergedCells.remove(region.id).getElement().removeFromParent(); overflownMergedCells.remove(region); @@ -3852,10 +3861,10 @@ private Cell getMergedCell(String key) { } private boolean setMergedCellValue(String key, String value, - String cellStyle, boolean needsMeasure) { + String cellStyle, String textColor, boolean needsMeasure) { Cell cell = getMergedCell(key); if (cell != null) { - cell.setValue(value, cellStyle, needsMeasure); + cell.setValue(value, cellStyle, textColor, needsMeasure); return true; } return false; @@ -4217,9 +4226,9 @@ public void updateTopLeftCellValues(List cellData2) { topLeftCells .get((cd.row - 1) * horizontalSplitPosition + cd.col - 1) - .setValue(cd.value, cd.cellStyle, cd.needsMeasure); + .setValue(cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); String key = toKey(cd.col, cd.row); - setMergedCellValue(key, cd.value, cd.cellStyle, + setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); if (cd.value == null) { cachedCellData.remove(key); @@ -4265,11 +4274,11 @@ private void updateCellData(int r1, int r2, int c1, int c2, c1 = row.get(0).getCol(); } } - row.get(cd.col - c1).setValue(cd.value, cd.cellStyle, + row.get(cd.col - c1).setValue(cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); } String key = toKey(cd.col, cd.row); - setMergedCellValue(key, cd.value, cd.cellStyle, cd.needsMeasure); + setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); if (cd.value == null) { cachedCellData.remove(key); } else { @@ -4291,7 +4300,7 @@ public void cellValuesUpdated(ArrayList updatedCellData) { } else { cachedCellData.put(key, cd); } - if (!setMergedCellValue(key, cd.value, cd.cellStyle, + if (!setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure)) { Cell cell = null; if (isCellRenderedInScrollPane(cd.col, cd.row)) { @@ -4302,7 +4311,7 @@ public void cellValuesUpdated(ArrayList updatedCellData) { } if (cell != null) { - cell.setValue(cd.value, cd.cellStyle, cd.needsMeasure); + cell.setValue(cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); cell.markAsOverflowDirty(); } int j = verticalSplitPosition > 0 ? 0 : firstColumnIndex; diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SpreadsheetWidget.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SpreadsheetWidget.java index 7ea319dee76..997eae87b8b 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SpreadsheetWidget.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SpreadsheetWidget.java @@ -508,7 +508,7 @@ public void execute() { // initial display only used single column width, // re-calculate with merged width cell.setValue(cell.getValue(), cell.getCellStyle(), - false); + cell.getTextColor(), false); } i++; } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/main/resources/test_sheets/custom_formatting_rainbow.xlsx b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/main/resources/test_sheets/custom_formatting_rainbow.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d63277c80075b4e5d5d8786239d4420bc4b8895f GIT binary patch literal 5048 zcmb7I2UJsAvknA8P)YA$g@tBaS7iEmIna6z!-Z?rT=IcDDMP*0t`nMsl*ucUs%--~?9$Z%x;S2MU?Z2k2R*nW=JI z6LmV;ZaY9gaZp?++Zd^mp8L*CRmPbu&w2K??E8D!U0%qXNbQR@G@pD#>zD<<(@77% zyj4YL`SH4b4Tgvhofs$K2sL1XFovDx;-4)qtE2{Mcl(% z-7O_`COGJM+363WS>F%w_aYvhN_FEeJLz?r`IwGaRgYx4K(mS>2Gi~-t94Jb8MiCU zdYmcS#vt;9B&T-_4E=5dXl*CCDS{5_i832lhHzX6f;`5ab6t!QNC5ynx@mhmazyu2 zcJ-Aydmw4U-=GYw`_Nqqd6>|&90TmmGErPpl?I$B#NTk00suHo1psLNEA~f8*qeLU zIC(-Zk-r}$wWt7LkZ|+!U5#1J)W_`K$ABB7-qElnk4{u&A(d`SG&Q!w)H)$rriCw0 z9>54bG7-v+I+t<_(cS}?f?K28BOJFSr5`WBkGFhr`=kU?ziH7tCOvRhTfbvUM=SMM z3>v{R;b{65PNCWwg!`jtK)5F!x z6Kd(^Ms`cJ_?9Cix0GMT96&r=pZh}+MT@yHd;z_TVB!!cQ%y;aIG2^|pUpnIdh_{? zLIcd^L}_Y$T_PnRysz&`YQk=@REN5&xP#Ie6e_|nh32!~+pblrdyBCjX6m|brU~%4 zzQC>D6;^xuNpd7z5j7&-P2px!bnJ~_5L&xjx(#pGsvx`7$5O=RqECI$h{+4xCv7hc zSj;8#OfqS?FG%;lZ}+jo23fg!*pMx9zBi_V6J}8eK0ni_mih4P{YCDMkbDUXh1H8Z zZ)!(OlnQdT0|un@G$)h3db9G?zvoQ-thO#!J|$}tbcrAKUVu}0gfSpypuigDN7bfAD!-cUD^_Vre$YMWUEfMy_*JM-$jYYkh?%BY z8ua+~6`7;WZ->OwAKv5a-QTyJ9h!^&@$ls~6&XYkA^Eo;(mPX4y8j(S77~cJouHn# zEIn+jb-X<6UF$csr z)jH696^X)!-H8Upr&6M;H9`|KfLlvGFW^>NSJiL@8j>~WI#6O^u#J3k?odAe8mpz*$Ea`{K`jbGbE_1bzd|eEg%U(EpE>UVctCf0_4e4Brt6Iv(d7y5lT{EY!HUP~-t zgU;bwH~o#56`y~W(}K3?9Gv2K25qj$w9Q;8?cS<2v58sb68H1pUG34IZ1z8Hs=#%l zvi?~>vjR%^QKXh(u9{4quQ7oAc+L-B`t9wyZuX*r(>2+)8u0827pV+Hbkbz5x7O=u8 zztW(7jGn{I1ITb?DusUiilgtYCD6>}RUxRM290)lFkv|YzsCVCMdy>|n$!nKIavsb z3oFngQ|Z^AIr{d5(|65)Y)jD@HRz_G#iiRLa3OI>AqZs(bT@DD@fC66PUKM11V)jD z@8+CMW5I*qQGRGGbU~RinJSwHd|se9)JzwiIFb>+ z{wLLGhgEl?K!`2?j!v^HpC#D_7Rqf4K!NyeHKPeNSOnxU00v~a%#XJ=?Bb4O55ZDI zv!|eHglr)=I(QcVPp1N7zxFwi^IQJhweN@oZDbmr4uhqLWY5PUSUmgFCZTf+HMh6b zfpKH6Jem#y)&;t=+E>SpcZZ9OmKfM17!G*e*-`6v9(wGbHJg>?;7Rv*V3AkmsiibX zee&6tA!l&~!<_l&y6Vt3ykdU)<5US`J*o>*<^DW7qC*BiIrDDEYZ-(o})#7C;7CgpIRfrKy4U4v6 zp9kWX@|6R!K5y4gr)vb6o6iK9HsBoA z{>H@eRt5+=B#zu#tE0iPM#YiX-djIe%um+ME|H|a{f`%E!r!m&2U!iuzN&SS{@YyX zoxy+QDslMW>>D5ephWUd$1LN|1IX9a!x81`>PS9e4v{M#gR8s1jt6LJK znh)RmPiCQ+9%~wIOAW|ly~*JT_LcCW_lETLR$TC|{gQSEULiXVzjr@#IGhA8_ZZ1) zvp=RECx7DdGiyJL&d&@!QO%JA5&R&!FVMZeg{{y9YSrr~6BJUFDj`}OSNyEAxw5`> z2u^c#ohNoBfbpqGa9pUA)~qxLsHPiQU{SarXE#-KaND06k7@Y`0kUHwEgOwo?BN3D zBU@3rmQ-R>3jJ9_PS@kwb&)I`C2@Z80zKozyPJ`xi3+C8U+#2fJ0;M5x<0oBF_~aEj%yd~oxafYFqoJZyfKGO z_!>21WOn1*mn5#1D>r%`H`e{-GFbXlm!|Grk9J7l5~_A< z8m;aD=WTGBM>l%U)O08iFFzQzV}(9HOX$q$3EHERQ5&^dSq=4NT6@?bp`Qlo=&;Ms zj-VNPT+0sqRK~~9@J>VfOpF1sczkpau_j@kk8-Ww+aI~~slghZK0^&%IDL_zlh-{S zF>1b-J&GP~51#vRlYLmParZ{#1OHXY)r=)=d=}T8p6AgE&ysqw4Rv|d9W(gM*OK&S zyO%#bh3qQ%7B+v`Mt|rGdTu!K^gzvsR%3duvEKss&9r1)<8oPWmYRvX>AXg6DRMHv z(T}}cVx>sJu;1?Se*D-DYH9vPpdL|dz67|;wtjuL^!bPR1U=vOmE#%olV{W;o+=nC z*zyArt@XjzpYU(L@V zR)WK*ECI4SVCJeD7-7m|kk-!2Aq z?JjrHZCpIUkgWmt_5j{AHGdcpBr3R76x zChs}hVp;x63#m)z9OI+s@VO7lv|y^Wb}h3;QLPz|eCOZ3X^|l(^Sgtd%YCF*LWdMC zHB~7nSpdJ9EQebFlhj>+j-+>l<>d_KT`Wy^w#Z+} for the full + * license. + */ +package com.vaadin.flow.component.spreadsheet.test; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.flow.component.spreadsheet.testbench.SheetCellElement; +import com.vaadin.flow.testutil.TestPath; + +@TestPath("vaadin-spreadsheet") +public class CustomFormattingIT extends AbstractSpreadsheetIT { + + // These values are specific to the test sheet + private static final int ROW_OFFSET = 6; + private static final int COL_OFFSET = 1; + private static final int ROW_COUNT = 8; + private static final int COL_COUNT = 8; + + // The 8 basic named colors, in order in the document, as CSS strings + private static final String[] NAMED_COLOR_CSS = { + "rgba(0, 0, 0, 1)", // black + "rgba(0, 0, 255, 1)", // blue + "rgba(0, 255, 255, 1)", // cyan + "rgba(0, 255, 0, 1)", // green + "rgba(255, 0, 0, 1)", // red + "rgba(255, 255, 255, 1)", // white + "rgba(255, 255, 0, 1)", // yellow + "rgba(255, 0, 255, 1)" // magenta + }; + + // The remaining 7 rows of indexed colors, as CSS strings + private static final String[] INDEXED_COLOR_CSS = new String[56]; + static { + // Color values in hex, taken from the modified CellFormatPart.java + // in vaadin-spreadsheet-flow. These are the 56 indexed Excel colors + final int[] rgb_hex = { + 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, + 0x800000, 0x008000, 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080, + 0x9999FF, 0x993366, 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, 0x0066CC, 0xCCCCFF, + 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, 0x800080, 0x800000, 0x008080, 0x0000FF, + 0x00CCFF, 0xCCFFFF, 0xCCFFCC, 0xFFFF99, 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, + 0x3366FF, 0x33CCCC, 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, 0x666699, 0x969696, + 0x003366, 0x339966, 0x003300, 0x333300, 0x993300, 0x993366, 0x333399, 0x333333 + }; + + // Convert table to CSS strings + for (int i = 0; i < 56; ++i) { + INDEXED_COLOR_CSS[i] = "rgba(" + + ((rgb_hex[i] >>> 16) & 0xff) + ", " + + ((rgb_hex[i] >>> 8) & 0xff) + ", " + + (rgb_hex[i] & 0xff) + ", 1)"; + } + } + + @Before + public void init() { + open(); + loadFile("custom_formatting_rainbow.xlsx"); + } + + private void validateAllColors() { + validateNamedColors(); + validateIndexedColors(); + } + + private void validateNamedColors() { + // Verify all basic colors + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(ROW_OFFSET, col + COL_OFFSET); + Assert.assertEquals(NAMED_COLOR_CSS[col], cell.getCssValue("color")); + } + } + + private void validateIndexedColors() { + // Verify all indexed colors (from second row of table onwards) + for (int row = 0; row < (ROW_COUNT - 1); ++row) { + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET + 1, col + COL_OFFSET); + Assert.assertEquals(INDEXED_COLOR_CSS[row * COL_COUNT + col], cell.getCssValue("color")); + } + } + } + + @Test + public void customFormatting_verifyBasicColorsPresent() { + validateNamedColors(); + } + + @Test + public void customFormatting_verifyIndexedColors() { + validateIndexedColors(); + } + + @Test + public void customFormatting_verifyAllFormulasValid() { + // Test initial conditions, make sure no cell is marked as invalid + for (int row = 0; row < ROW_COUNT; ++row) { + for (int col = 0; col < ROW_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + Assert.assertFalse(cell.hasInvalidFormulaIndicator()); + } + } + } + + @Test + public void customFormatting_verifyAllFormulasValidAfterValueChange_Positive() { + // Control cell other cells copy their value from + SheetCellElement testValueCell = getSpreadsheet().getCellAt(4, 2); + + // Test positive value + testValueCell.setValue("98"); + for (int row = 0; row < ROW_COUNT; ++row) { + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + Assert.assertFalse(cell.hasInvalidFormulaIndicator()); + Assert.assertEquals(Double.parseDouble(cell.getValue()), + Double.parseDouble(testValueCell.getValue()), 0d); + } + } + + validateAllColors(); + } + + @Test + public void customFormatting_verifyAllFormulasValidAfterValueChange_Negative() { + // Control cell other cells copy their value from + SheetCellElement testValueCell = getSpreadsheet().getCellAt(4, 2); + + // Test negative value + testValueCell.setValue("-75"); + for (int row = 0; row < ROW_COUNT; ++row) { + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + Assert.assertFalse(cell.hasInvalidFormulaIndicator()); + Assert.assertEquals(Double.parseDouble(cell.getValue() + .replace('(', ' ').replace(')', ' ')), + -Double.parseDouble(testValueCell.getValue()), 0d); + } + } + + validateAllColors(); + } + + @Test + public void customFormatting_verifyAllFormulasValidAfterValueChange_Zero() { + // Control cell other cells copy their value from + SheetCellElement testValueCell = getSpreadsheet().getCellAt(4, 2); + + // Test zero value + testValueCell.setValue("0"); + for (int row = 0; row < ROW_COUNT; ++row) { + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + Assert.assertFalse(cell.hasInvalidFormulaIndicator()); + Assert.assertEquals("===", cell.getValue()); + } + } + + validateAllColors(); + } + + @Test + public void customFormatting_verifyAllFormulasValidAfterValueChange_String() { + // Control cell other cells copy their value from + SheetCellElement testValueCell = getSpreadsheet().getCellAt(4, 2); + + // Test string value + testValueCell.setValue("test"); + for (int row = 0; row < ROW_COUNT; ++row) { + for (int col = 0; col < COL_COUNT; ++col) { + SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + Assert.assertFalse(cell.hasInvalidFormulaIndicator()); + Assert.assertEquals("\"test\"", cell.getValue()); + } + } + + validateAllColors(); + } +} diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java index 9d583fac466..817edecdd3f 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java @@ -240,7 +240,13 @@ protected CellData createCellDataForCell(Cell cell) { cell.getColumnIndex() + 1, cell.getRowIndex() + 1); } + } + } + if (formatter instanceof CustomDataFormatter) { + String color = ((CustomDataFormatter)formatter).getCellTextColor(cell); + if (color != null) { + cellData.textColor = color; } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java index 69a07986b0a..a1c239d01a7 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java @@ -8,11 +8,13 @@ */ package com.vaadin.flow.component.spreadsheet; +import java.awt.Color; import java.io.Serializable; import java.util.Locale; import java.util.regex.Pattern; import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; @@ -127,15 +129,48 @@ private String formatNumericValueUsingFormatPart(Cell cell, if (isOnlyLiteralFormat(format)) { // CellFormat can format literals correctly - return formatTextUsingCellFormat(cell, format); + return formatTextUsingCellFormat(cell, format).text; } else { // DataFormatter can format numbers correctly return super.formatCellValue(cell, evaluator, cfEvaluator); } } - private String formatTextUsingCellFormat(Cell cell, String format) { - return CellFormat.getInstance(locale, format).apply(cell).text; + private CellFormatResult formatTextUsingCellFormat(Cell cell, String format) { + return CellFormat.getInstance(locale, format).apply(cell); + } + + /** + * Get the applicable text color for the cell. This uses Apache POI's + * CellFormat logic, which parses and evaluates the cell's format string + * against the cell's current value. + * + * @param cell The cell to get the applicable custom formatting text color for. + * @return a CSS color value string, or null if no text color should be applied. + */ + public String getCellTextColor(Cell cell) { + try { + final String format = cell.getCellStyle().getDataFormatString(); + if (format == null || format.isEmpty() || isGeneralFormat(format)) { + return null; + } + + CellFormatResult result = formatTextUsingCellFormat(cell, format); + + if (result.textColor == null) { + return null; + } + + Color color = result.textColor; // AWT color value returned by POI + + // rgb(N,N,N) turned out to be the most reliably transmitted string + // in testing + String css = "rgb(" + color.getRed() + "," + color.getGreen() + "," + + color.getBlue() + ")"; + return css; + } catch(Exception e) { + return null; + } } private String getNumericFormat(double value, String[] formatParts) { @@ -181,6 +216,6 @@ private String formatStringCellValue(Cell cell, String formatString, return ""; } - return formatTextUsingCellFormat(cell, formatString); + return formatTextUsingCellFormat(cell, formatString).text; } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/client/CellData.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/client/CellData.java index 95bae9c7bca..05f67e0752b 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/client/CellData.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/client/CellData.java @@ -22,6 +22,7 @@ public class CellData implements Serializable { public String formulaValue; public String originalValue; public String cellStyle = "cs0"; + public String textColor; public boolean locked = false; public boolean needsMeasure; public boolean isPercentage; @@ -56,7 +57,7 @@ public boolean equals(Object obj) { @Override public String toString() { return new StringBuilder().append("r").append(row).append("c") - .append(col).append(cellStyle).append("|").append(value) - .toString(); + .append(col).append(cellStyle).append("tc").append(textColor) + .append("|").append(value).toString(); } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java new file mode 100644 index 00000000000..730416e859c --- /dev/null +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -0,0 +1,715 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ss.format; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.util.CodepointsUtil; +import org.apache.poi.util.LocaleUtil; + +import javax.swing.*; + +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.poi.ss.format.CellFormatter.quote; + +/* + * Note: This file has been overridden by Vaadin to allow proper formatting + * of all available colors. This override should be removed once the color + * formatting has been fixed upstream in Apache POI. + * + * This class has been heavily modified in order to provide full support + * for colors supported by Excel. + * + * As of February 2025, Apache POI's implementation of this class does not + * provide Excel-correct color values, does not support indexed color at all + * ánd is missing the definition for magenta completely. + * + * TODO: when upgrading Apache POI, check to see if this class has been + * updated with correct color support! + */ + +/** + * Objects of this class represent a single part of a cell format expression. + * Each cell can have up to four of these for positive, zero, negative, and text + * values. + *

+ * Each format part can contain a color, a condition, and will always contain a + * format specification. For example {@code "[Red][>=10]#"} has a color + * ({@code [Red]}), a condition ({@code >=10}) and a format specification + * ({@code #}). + *

+ * This class also contains patterns for matching the subparts of format + * specification. These are used internally, but are made public in case other + * code has use for them. + */ +@SuppressWarnings("RegExpRepeatedSpace") +public class CellFormatPart { + private static final Logger LOG = LogManager.getLogger(CellFormatPart.class); + + static final Map NAMED_COLORS; + static final List INDEXED_COLORS; + + private final Color color; + private final CellFormatCondition condition; + private final CellFormatter format; + private final CellFormatType type; + + /** Pattern for the color part of a cell format part. */ + public static final Pattern COLOR_PAT; + /** Pattern for the condition part of a cell format part. */ + public static final Pattern CONDITION_PAT; + /** Pattern for the format specification part of a cell format part. */ + public static final Pattern SPECIFICATION_PAT; + /** Pattern for the currency symbol part of a cell format part */ + public static final Pattern CURRENCY_PAT; + /** Pattern for an entire cell single part. */ + public static final Pattern FORMAT_PAT; + + /** Within {@link #FORMAT_PAT}, the group number for the matched color. */ + public static final int COLOR_GROUP; + + /** + * Within {@link #FORMAT_PAT}, the group number for the operator in the + * condition. + */ + public static final int CONDITION_OPERATOR_GROUP; + /** + * Within {@link #FORMAT_PAT}, the group number for the value in the + * condition. + */ + public static final int CONDITION_VALUE_GROUP; + /** + * Within {@link #FORMAT_PAT}, the group number for the format + * specification. + */ + public static final int SPECIFICATION_GROUP; + + static { + // Build indexed color list based on this table + // https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/ + INDEXED_COLORS = List.of( + new Color(0x000000), // Color 1 / black + new Color(0xFFFFFF), // Color 2 / white + new Color(0xFF0000), // Color 3 / red + new Color(0x00FF00), // Color 4 / green + new Color(0x0000FF), // Color 5 / blue + new Color(0xFFFF00), // Color 6 / yellow + new Color(0xFF00FF), // Color 7 / magenta + new Color(0x00FFFF), // Color 8 / cyan + new Color(0x800000), // Color 9 + new Color(0x008000), // Color 10 + new Color(0x000080), // Color 11 + new Color(0x808000), // Color 12 + new Color(0x800080), // Color 13 + new Color(0x008080), // Color 14 + new Color(0xC0C0C0), // Color 15 + new Color(0x808080), // Color 16 + new Color(0x9999FF), // Color 17 + new Color(0x993366), // Color 18 + new Color(0xFFFFCC), // Color 19 + new Color(0xCCFFFF), // Color 20 + new Color(0x660066), // Color 21 + new Color(0xFF8080), // Color 22 + new Color(0x0066CC), // Color 23 + new Color(0xCCCCFF), // Color 24 + new Color(0x000080), // Color 25 + new Color(0xFF00FF), // Color 26 + new Color(0xFFFF00), // Color 27 + new Color(0x00FFFF), // Color 28 + new Color(0x800080), // Color 29 + new Color(0x800000), // Color 30 + new Color(0x008080), // Color 31 + new Color(0x0000FF), // Color 32 + new Color(0x00CCFF), // Color 33 + new Color(0xCCFFFF), // Color 34 + new Color(0xCCFFCC), // Color 35 + new Color(0xFFFF99), // Color 36 + new Color(0x99CCFF), // Color 37 + new Color(0xFF99CC), // Color 38 + new Color(0xCC99FF), // Color 39 + new Color(0xFFCC99), // Color 40 + new Color(0x3366FF), // Color 41 + new Color(0x33CCCC), // Color 42 + new Color(0x99CC00), // Color 43 + new Color(0xFFCC00), // Color 44 + new Color(0xFF9900), // Color 45 + new Color(0xFF6600), // Color 46 + new Color(0x666699), // Color 47 + new Color(0x969696), // Color 48 + new Color(0x003366), // Color 49 + new Color(0x339966), // Color 50 + new Color(0x003300), // Color 51 + new Color(0x333300), // Color 52 + new Color(0x993300), // Color 53 + new Color(0x993366), // Color 54 + new Color(0x333399), // Color 55 + new Color(0x333333) // Color 56 + ); + + // Build named color list based on HSSFColorPredefined, just as + // Apache POI does it originally. This gives us a wider range + // of acceptable colors, but also puts out outside the acceptable + // color range of Excel. + NAMED_COLORS = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + + for (HSSFColor.HSSFColorPredefined color : HSSFColor.HSSFColorPredefined.values()) { + String name = color.name().toLowerCase(); + short[] rgb = color.getTriplet(); + Color c = new Color(rgb[0], rgb[1], rgb[2]); + NAMED_COLORS.put(name, c); + if (name.indexOf("_percent") > 0) { + NAMED_COLORS.put(name.replace("_percent", "%") + .replaceAll("\\_", " "), c); + } + if (name.indexOf('_') > 0) { + NAMED_COLORS.put(name.replaceAll("\\_", " "), c); + } + } + + // Replace the standard colors with standard values + // to support Excel like functionality + NAMED_COLORS.put("black", INDEXED_COLORS.get(0)); + NAMED_COLORS.put("white", INDEXED_COLORS.get(1)); + NAMED_COLORS.put("red", INDEXED_COLORS.get(2)); + NAMED_COLORS.put("green", INDEXED_COLORS.get(3)); + NAMED_COLORS.put("blue", INDEXED_COLORS.get(4)); + NAMED_COLORS.put("yellow", INDEXED_COLORS.get(5)); + NAMED_COLORS.put("magenta", INDEXED_COLORS.get(6)); + NAMED_COLORS.put("cyan", INDEXED_COLORS.get(7)); + + // A condition specification + String condition = "([<>=]=?|!=|<>) # The operator\n" + + " \\s*(-?([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]*))\\s* # The constant to test against\n"; + + // A currency symbol / string, in a specific locale + String currency = "(\\[\\$.{0,3}(-[0-9a-f]{3,4})?])"; + + // A number specification + // Note: careful that in something like ##, that the trailing comma is not caught up in the integer part + + // A part of a specification + //noinspection RegExpRedundantEscape + String part = "\\\\. # Quoted single character\n" + + "|\"([^\\\\\"]|\\\\.)*\" # Quoted string of characters (handles escaped quotes like \\\") \n" + + "|"+currency+" # Currency symbol in a given locale\n" + + "|_. # Space as wide as a given character\n" + + "|\\*. # Repeating fill character\n" + + "|@ # Text: cell text\n" + + "|([0?\\#][0?\\#,]*) # Number: digit + other digits and commas\n" + + "|e[-+] # Number: Scientific: Exponent\n" + + "|m{1,5} # Date: month or minute spec\n" + + "|d{1,4} # Date: day/date spec\n" + + "|y{2,4} # Date: year spec\n" + + "|h{1,2} # Date: hour spec\n" + + "|s{1,2} # Date: second spec\n" + + "|am?/pm? # Date: am/pm spec\n" + + "|\\[h{1,2}] # Elapsed time: hour spec\n" + + "|\\[m{1,2}] # Elapsed time: minute spec\n" + + "|\\[s{1,2}] # Elapsed time: second spec\n" + + "|[^;] # A character\n" + ""; + + // Build the color code matching expression. + // We should match any named color in the set as well as a string in the form + // of "Color 8" or "Color 15". + String color = "\\[("; + for (String key : NAMED_COLORS.keySet()) { + // Escape special characters in the color name + color += key.replaceAll("([^a-zA-Z0-9])", "\\\\$1") + "|"; + } + // Match the indexed color table + color += "color\\ [0-9]+)\\]"; + + String format = "(?:" + color + ")? # Text color\n" + + "(?:\\[" + condition + "])? # Condition\n" + + // see https://msdn.microsoft.com/en-ca/goglobal/bb964664.aspx and https://bz.apache.org/ooo/show_bug.cgi?id=70003 + // we ignore these for now though + "(?:\\[\\$-[0-9a-fA-F]+])? # Optional locale id, ignored currently\n" + + "((?:" + part + ")+) # Format spec\n"; + + int flags = Pattern.COMMENTS | Pattern.CASE_INSENSITIVE; + COLOR_PAT = Pattern.compile(color, flags); + CONDITION_PAT = Pattern.compile(condition, flags); + SPECIFICATION_PAT = Pattern.compile(part, flags); + CURRENCY_PAT = Pattern.compile(currency, flags); + FORMAT_PAT = Pattern.compile(format, flags); + + // Calculate the group numbers of important groups. (They shift around + // when the pattern is changed; this way we figure out the numbers by + // experimentation.) + + COLOR_GROUP = findGroup(FORMAT_PAT, "[Blue]@", "Blue"); + CONDITION_OPERATOR_GROUP = findGroup(FORMAT_PAT, "[>=1]@", ">="); + CONDITION_VALUE_GROUP = findGroup(FORMAT_PAT, "[>=1]@", "1"); + SPECIFICATION_GROUP = findGroup(FORMAT_PAT, "[Blue][>1]\\a ?", "\\a ?"); + + // Once patterns have been compiled, add indexed colors to + // NAMED_COLORS so they can be easily picked up by an unmodified + // getColor() implementation + for (int i = 0; i < INDEXED_COLORS.size(); ++i) { + NAMED_COLORS.put("color " + (i + 1), INDEXED_COLORS.get(i)); + } + // NOTE: the INDEXED_COLORS list is retained for future use, even + // though it is not currently utilized outside the static + // initialization logic. + } + + interface PartHandler { + String handlePart(Matcher m, String part, CellFormatType type, + StringBuffer desc); + } + + /** + * Create an object to represent a format part. + * + * @param desc The string to parse. + */ + public CellFormatPart(String desc) { + this(LocaleUtil.getUserLocale(), desc); + } + + /** + * Create an object to represent a format part. + * + * @param locale The locale to use. + * @param desc The string to parse. + */ + public CellFormatPart(Locale locale, String desc) { + Matcher m = FORMAT_PAT.matcher(desc); + if (!m.matches()) { + throw new IllegalArgumentException("Unrecognized format: " + quote( + desc)); + } + color = getColor(m); + condition = getCondition(m); + type = getCellFormatType(m); + format = getFormatter(locale, m); + } + + /** + * Returns {@code true} if this format part applies to the given value. If + * the value is a number and this is part has a condition, returns + * {@code true} only if the number passes the condition. Otherwise, this + * always return {@code true}. + * + * @param valueObject The value to evaluate. + * + * @return {@code true} if this format part applies to the given value. + */ + public boolean applies(Object valueObject) { + if (condition == null || !(valueObject instanceof Number)) { + if (valueObject == null) + throw new NullPointerException("valueObject"); + return true; + } else { + Number num = (Number) valueObject; + return condition.pass(num.doubleValue()); + } + } + + /** + * Returns the number of the first group that is the same as the marker + * string. Starts from group 1. + * + * @param pat The pattern to use. + * @param str The string to match against the pattern. + * @param marker The marker value to find the group of. + * + * @return The matching group number. + * + * @throws IllegalArgumentException No group matches the marker. + */ + private static int findGroup(Pattern pat, String str, String marker) { + Matcher m = pat.matcher(str); + if (!m.find()) + throw new IllegalArgumentException( + "Pattern \"" + pat.pattern() + "\" doesn't match \"" + str + + "\""); + for (int i = 1; i <= m.groupCount(); i++) { + String grp = m.group(i); + if (grp != null && grp.equals(marker)) + return i; + } + throw new IllegalArgumentException( + "\"" + marker + "\" not found in \"" + pat.pattern() + "\""); + } + + /** + * Returns the color specification from the matcher, or {@code null} if + * there is none. + * + * @param m The matcher for the format part. + * + * @return The color specification or {@code null}. + */ + private static Color getColor(Matcher m) { + String cdesc = m.group(COLOR_GROUP); + + if (cdesc == null || cdesc.length() == 0) { + return null; + } + + Color c = NAMED_COLORS.get(cdesc); + if (c == null) { + LOG.error("Unknown color: {}", quote(cdesc)); + } + return c; + } + + /** + * Returns the condition specification from the matcher, or {@code null} if + * there is none. + * + * @param m The matcher for the format part. + * + * @return The condition specification or {@code null}. + */ + private CellFormatCondition getCondition(Matcher m) { + String mdesc = m.group(CONDITION_OPERATOR_GROUP); + if (mdesc == null || mdesc.length() == 0) + return null; + return CellFormatCondition.getInstance(m.group( + CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP)); + } + + /** + * Returns the CellFormatType object implied by the format specification for + * the format part. + * + * @param matcher The matcher for the format part. + * + * @return The CellFormatType. + */ + private CellFormatType getCellFormatType(Matcher matcher) { + String fdesc = matcher.group(SPECIFICATION_GROUP); + return formatType(fdesc); + } + + /** + * Returns the formatter object implied by the format specification for the + * format part. + * + * @param matcher The matcher for the format part. + * + * @return The formatter. + */ + private CellFormatter getFormatter(Locale locale, Matcher matcher) { + String fdesc = matcher.group(SPECIFICATION_GROUP); + + // For now, we don't support localised currencies, so simplify if there + Matcher currencyM = CURRENCY_PAT.matcher(fdesc); + if (currencyM.find()) { + String currencyPart = currencyM.group(1); + String currencyRepl; + if (currencyPart.startsWith("[$-")) { + // Default $ in a different locale + currencyRepl = "$"; + } else if (!currencyPart.contains("-")) { + // Accounting formats such as USD [$USD] + currencyRepl = currencyPart.substring(2, currencyPart.indexOf("]")); + } else { + currencyRepl = currencyPart.substring(2, currencyPart.lastIndexOf('-')); + } + fdesc = fdesc.replace(currencyPart, currencyRepl); + } + + // Build a formatter for this simplified string + return type.formatter(locale, fdesc); + } + + /** + * Returns the type of format. + * + * @param fdesc The format specification + * + * @return The type of format. + */ + private CellFormatType formatType(String fdesc) { + fdesc = fdesc.trim(); + if (fdesc.isEmpty() || fdesc.equalsIgnoreCase("General")) + return CellFormatType.GENERAL; + + Matcher m = SPECIFICATION_PAT.matcher(fdesc); + boolean couldBeDate = false; + boolean seenZero = false; + while (m.find()) { + String repl = m.group(0); + Iterator codePoints = CodepointsUtil.iteratorFor(repl); + if (codePoints.hasNext()) { + String c1 = codePoints.next(); + + switch (c1) { + case "@": + return CellFormatType.TEXT; + case "d": + case "D": + case "y": + case "Y": + return CellFormatType.DATE; + case "h": + case "H": + case "m": + case "M": + case "s": + case "S": + // These can be part of date, or elapsed + couldBeDate = true; + break; + case "0": + // This can be part of date, elapsed, or number + seenZero = true; + break; + case "[": + String c2 = null; + if (codePoints.hasNext()) + c2 = codePoints.next().toLowerCase(Locale.ROOT); + if ("h".equals(c2) || "m".equals(c2) || "s".equals(c2)) { + return CellFormatType.ELAPSED; + } + if ("$".equals(c2)) { + // Localised currency + return CellFormatType.NUMBER; + } + // Something else inside [] which isn't supported! + throw new IllegalArgumentException("Unsupported [] format block '" + + repl + "' in '" + fdesc + "' with c2: " + c2); + case "#": + case "?": + return CellFormatType.NUMBER; + } + } + } + + // Nothing definitive was found, so we figure out it deductively + if (couldBeDate) + return CellFormatType.DATE; + if (seenZero) + return CellFormatType.NUMBER; + return CellFormatType.TEXT; + } + + /** + * Returns a version of the original string that has any special characters + * quoted (or escaped) as appropriate for the cell format type. The format + * type object is queried to see what is special. + * + * @param repl The original string. + * @param type The format type representation object. + * + * @return A version of the string with any special characters replaced. + * + * @see CellFormatType#isSpecial(char) + */ + static String quoteSpecial(String repl, CellFormatType type) { + StringBuilder sb = new StringBuilder(); + PrimitiveIterator.OfInt codePoints = CodepointsUtil.primitiveIterator(repl); + + int codepoint; + while (codePoints.hasNext()) { + codepoint = codePoints.nextInt(); + if (codepoint == '\'' && type.isSpecial('\'')) { + sb.append('\u0000'); + continue; + } + + char[] chars = Character.toChars(codepoint); + boolean special = type.isSpecial(chars[0]); + if (special) + sb.append('\''); + sb.append(chars); + if (special) + sb.append('\''); + } + return sb.toString(); + } + + /** + * Apply this format part to the given value. This returns a {@link + * CellFormatResult} object with the results. + * + * @param value The value to apply this format part to. + * + * @return A {@link CellFormatResult} object containing the results of + * applying the format to the value. + */ + public CellFormatResult apply(Object value) { + boolean applies = applies(value); + String text; + Color textColor; + if (applies) { + text = format.format(value); + textColor = color; + } else { + text = format.simpleFormat(value); + textColor = null; + } + return new CellFormatResult(applies, text, textColor); + } + + /** + * Apply this format part to the given value, applying the result to the + * given label. + * + * @param label The label + * @param value The value to apply this format part to. + * + * @return {@code true} if the + */ + public CellFormatResult apply(JLabel label, Object value) { + CellFormatResult result = apply(value); + label.setText(result.text); + if (result.textColor != null) { + label.setForeground(result.textColor); + } + return result; + } + + /** + * Returns the CellFormatType object implied by the format specification for + * the format part. + * + * @return The CellFormatType. + */ + CellFormatType getCellFormatType() { + return type; + } + + /** + * Returns {@code true} if this format part has a condition. + * + * @return {@code true} if this format part has a condition. + */ + boolean hasCondition() { + return condition != null; + } + + public static StringBuffer parseFormat(String fdesc, CellFormatType type, + PartHandler partHandler) { + + // Quoting is very awkward. In the Java classes, quoting is done + // between ' chars, with '' meaning a single ' char. The problem is that + // in Excel, it is legal to have two adjacent escaped strings. For + // example, consider the Excel format "\a\b#". The naive (and easy) + // translation into Java DecimalFormat is "'a''b'#". For the number 17, + // in Excel you would get "ab17", but in Java it would be "a'b17" -- the + // '' is in the middle of the quoted string in Java. So the trick we + // use is this: When we encounter a ' char in the Excel format, we + // output a \u0000 char into the string. Now we know that any '' in the + // output is the result of two adjacent escaped strings. So after the + // main loop, we have to do two passes: One to eliminate any '' + // sequences, to make "'a''b'" become "'ab'", and another to replace any + // \u0000 with '' to mean a quote char. Oy. + // + // For formats that don't use "'" we don't do any of this + Matcher m = SPECIFICATION_PAT.matcher(fdesc); + StringBuffer fmt = new StringBuffer(); + while (m.find()) { + String part = group(m, 0); + if (part.length() > 0) { + String repl = partHandler.handlePart(m, part, type, fmt); + if (repl == null) { + switch (part.charAt(0)) { + case '\"': + repl = quoteSpecial(part.substring(1, + part.length() - 1), type); + break; + case '\\': + repl = quoteSpecial(part.substring(1), type); + break; + case '_': + repl = " "; + break; + case '*': //!! We don't do this for real, we just put in 3 of them + repl = expandChar(part); + break; + default: + repl = part; + break; + } + } + m.appendReplacement(fmt, Matcher.quoteReplacement(repl)); + } + } + m.appendTail(fmt); + + if (type.isSpecial('\'')) { + // Now the next pass for quoted characters: Remove '' chars, making "'a''b'" into "'ab'" + int pos = 0; + while ((pos = fmt.indexOf("''", pos)) >= 0) { + fmt.delete(pos, pos + 2); + if (partHandler instanceof CellDateFormatter.DatePartHandler) { + CellDateFormatter.DatePartHandler datePartHandler = (CellDateFormatter.DatePartHandler) partHandler; + datePartHandler.updatePositions(pos, -2); + } + } + + // Now the final pass for quoted chars: Replace any \u0000 with '' + pos = 0; + while ((pos = fmt.indexOf("\u0000", pos)) >= 0) { + fmt.replace(pos, pos + 1, "''"); + if (partHandler instanceof CellDateFormatter.DatePartHandler) { + CellDateFormatter.DatePartHandler datePartHandler = (CellDateFormatter.DatePartHandler) partHandler; + datePartHandler.updatePositions(pos, 1); + } + } + } + + return fmt; + } + + /** + * Expands a character. This is only partly done, because we don't have the + * correct info. In Excel, this would be expanded to fill the rest of the + * cell, but we don't know, in general, what the "rest of the cell" is. + * + * @param part The character to be repeated is the second character in this + * string. + * + * @return The character repeated three times. + */ + static String expandChar(String part) { + List codePoints = new ArrayList<>(); + CodepointsUtil.iteratorFor(part).forEachRemaining(codePoints::add); + if (codePoints.size() < 2) throw new IllegalArgumentException("Expected part string to have at least 2 chars"); + String ch = codePoints.get(1); + return ch + ch + ch; + } + + /** + * Returns the string from the group, or {@code ""} if the group is + * {@code null}. + * + * @param m The matcher. + * @param g The group number. + * + * @return The group or {@code ""}. + */ + public static String group(Matcher m, int g) { + String str = m.group(g); + return (str == null ? "" : str); + } + + public String toString() { + return format.format; + } +} From adf94aa28a746c1562c53e21a18e67b6dba6a685 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Tue, 25 Feb 2025 04:25:28 +0200 Subject: [PATCH 2/8] apply automatic formatting ...thus making some of the code look really, really ugly. --- .../vaadin/addon/spreadsheet/client/Cell.java | 3 +- .../addon/spreadsheet/client/SheetWidget.java | 22 +- .../spreadsheet/test/CustomFormattingIT.java | 81 ++-- .../spreadsheet/CellValueManager.java | 3 +- .../spreadsheet/CustomDataFormatter.java | 18 +- .../apache/poi/ss/format/CellFormatPart.java | 393 ++++++++++-------- 6 files changed, 281 insertions(+), 239 deletions(-) diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java index 2a37a88820d..88fa7a60681 100755 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/Cell.java @@ -233,7 +233,8 @@ public String getValue() { return value; } - public void setValue(String value, String cellStyle, String textColor, boolean needsMeasure) { + public void setValue(String value, String cellStyle, String textColor, + boolean needsMeasure) { if (!this.cellStyle.equals(cellStyle)) { this.cellStyle = cellStyle; updateClassName(); diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java index 4cee2ccf02e..f3f1d97659b 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-client/src/main/java/com/vaadin/addon/spreadsheet/client/SheetWidget.java @@ -3144,7 +3144,7 @@ else if (vScrollDiff < 0) { // unless this is checked for. continue; } - + int cIndex = cell.getCol(); // scroll right @@ -3733,7 +3733,7 @@ public void removeMergedRegion(MergedRegion region, int ruleIndex) { Cell originalCell = getCell(region.col1, region.row1); if (originalCell != null) { originalCell.setValue(mCell.getValue(), mCell.getCellStyle(), - originalCell.getTextColor(),false); + originalCell.getTextColor(), false); } mergedCells.remove(region.id).getElement().removeFromParent(); overflownMergedCells.remove(region); @@ -4223,10 +4223,10 @@ public void updateTopLeftCellValues(List cellData2) { Iterator i = cellData2.iterator(); while (i.hasNext()) { CellData cd = i.next(); - topLeftCells - .get((cd.row - 1) * horizontalSplitPosition + cd.col - - 1) - .setValue(cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); + topLeftCells.get( + (cd.row - 1) * horizontalSplitPosition + cd.col - 1) + .setValue(cd.value, cd.cellStyle, cd.textColor, + cd.needsMeasure); String key = toKey(cd.col, cd.row); setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); @@ -4274,11 +4274,12 @@ private void updateCellData(int r1, int r2, int c1, int c2, c1 = row.get(0).getCol(); } } - row.get(cd.col - c1).setValue(cd.value, cd.cellStyle, cd.textColor, - cd.needsMeasure); + row.get(cd.col - c1).setValue(cd.value, cd.cellStyle, + cd.textColor, cd.needsMeasure); } String key = toKey(cd.col, cd.row); - setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); + setMergedCellValue(key, cd.value, cd.cellStyle, cd.textColor, + cd.needsMeasure); if (cd.value == null) { cachedCellData.remove(key); } else { @@ -4311,7 +4312,8 @@ public void cellValuesUpdated(ArrayList updatedCellData) { } if (cell != null) { - cell.setValue(cd.value, cd.cellStyle, cd.textColor, cd.needsMeasure); + cell.setValue(cd.value, cd.cellStyle, cd.textColor, + cd.needsMeasure); cell.markAsOverflowDirty(); } int j = verticalSplitPosition > 0 ? 0 : firstColumnIndex; diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java index 5782cfb4190..57e4f685f7d 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java @@ -25,15 +25,14 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { private static final int COL_COUNT = 8; // The 8 basic named colors, in order in the document, as CSS strings - private static final String[] NAMED_COLOR_CSS = { - "rgba(0, 0, 0, 1)", // black - "rgba(0, 0, 255, 1)", // blue - "rgba(0, 255, 255, 1)", // cyan - "rgba(0, 255, 0, 1)", // green - "rgba(255, 0, 0, 1)", // red - "rgba(255, 255, 255, 1)", // white - "rgba(255, 255, 0, 1)", // yellow - "rgba(255, 0, 255, 1)" // magenta + private static final String[] NAMED_COLOR_CSS = { "rgba(0, 0, 0, 1)", // black + "rgba(0, 0, 255, 1)", // blue + "rgba(0, 255, 255, 1)", // cyan + "rgba(0, 255, 0, 1)", // green + "rgba(255, 0, 0, 1)", // red + "rgba(255, 255, 255, 1)", // white + "rgba(255, 255, 0, 1)", // yellow + "rgba(255, 0, 255, 1)" // magenta }; // The remaining 7 rows of indexed colors, as CSS strings @@ -41,22 +40,22 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { static { // Color values in hex, taken from the modified CellFormatPart.java // in vaadin-spreadsheet-flow. These are the 56 indexed Excel colors - final int[] rgb_hex = { - 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, - 0x800000, 0x008000, 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080, - 0x9999FF, 0x993366, 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, 0x0066CC, 0xCCCCFF, - 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, 0x800080, 0x800000, 0x008080, 0x0000FF, - 0x00CCFF, 0xCCFFFF, 0xCCFFCC, 0xFFFF99, 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, - 0x3366FF, 0x33CCCC, 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, 0x666699, 0x969696, - 0x003366, 0x339966, 0x003300, 0x333300, 0x993300, 0x993366, 0x333399, 0x333333 - }; + final int[] rgb_hex = { 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, + 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0x800000, 0x008000, + 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080, + 0x9999FF, 0x993366, 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, + 0x0066CC, 0xCCCCFF, 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, + 0x800080, 0x800000, 0x008080, 0x0000FF, 0x00CCFF, 0xCCFFFF, + 0xCCFFCC, 0xFFFF99, 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, + 0x3366FF, 0x33CCCC, 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, + 0x666699, 0x969696, 0x003366, 0x339966, 0x003300, 0x333300, + 0x993300, 0x993366, 0x333399, 0x333333 }; // Convert table to CSS strings for (int i = 0; i < 56; ++i) { - INDEXED_COLOR_CSS[i] = "rgba(" + - ((rgb_hex[i] >>> 16) & 0xff) + ", " + - ((rgb_hex[i] >>> 8) & 0xff) + ", " + - (rgb_hex[i] & 0xff) + ", 1)"; + INDEXED_COLOR_CSS[i] = "rgba(" + ((rgb_hex[i] >>> 16) & 0xff) + ", " + + ((rgb_hex[i] >>> 8) & 0xff) + ", " + (rgb_hex[i] & 0xff) + + ", 1)"; } } @@ -74,8 +73,10 @@ private void validateAllColors() { private void validateNamedColors() { // Verify all basic colors for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(ROW_OFFSET, col + COL_OFFSET); - Assert.assertEquals(NAMED_COLOR_CSS[col], cell.getCssValue("color")); + SheetCellElement cell = getSpreadsheet().getCellAt(ROW_OFFSET, + col + COL_OFFSET); + Assert.assertEquals(NAMED_COLOR_CSS[col], + cell.getCssValue("color")); } } @@ -83,8 +84,10 @@ private void validateIndexedColors() { // Verify all indexed colors (from second row of table onwards) for (int row = 0; row < (ROW_COUNT - 1); ++row) { for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET + 1, col + COL_OFFSET); - Assert.assertEquals(INDEXED_COLOR_CSS[row * COL_COUNT + col], cell.getCssValue("color")); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET + 1, col + COL_OFFSET); + Assert.assertEquals(INDEXED_COLOR_CSS[row * COL_COUNT + col], + cell.getCssValue("color")); } } } @@ -104,7 +107,8 @@ public void customFormatting_verifyAllFormulasValid() { // Test initial conditions, make sure no cell is marked as invalid for (int row = 0; row < ROW_COUNT; ++row) { for (int col = 0; col < ROW_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET, col + COL_OFFSET); Assert.assertFalse(cell.hasInvalidFormulaIndicator()); } } @@ -119,10 +123,11 @@ public void customFormatting_verifyAllFormulasValidAfterValueChange_Positive() { testValueCell.setValue("98"); for (int row = 0; row < ROW_COUNT; ++row) { for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET, col + COL_OFFSET); Assert.assertFalse(cell.hasInvalidFormulaIndicator()); Assert.assertEquals(Double.parseDouble(cell.getValue()), - Double.parseDouble(testValueCell.getValue()), 0d); + Double.parseDouble(testValueCell.getValue()), 0d); } } @@ -138,11 +143,13 @@ public void customFormatting_verifyAllFormulasValidAfterValueChange_Negative() { testValueCell.setValue("-75"); for (int row = 0; row < ROW_COUNT; ++row) { for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET, col + COL_OFFSET); Assert.assertFalse(cell.hasInvalidFormulaIndicator()); - Assert.assertEquals(Double.parseDouble(cell.getValue() - .replace('(', ' ').replace(')', ' ')), - -Double.parseDouble(testValueCell.getValue()), 0d); + Assert.assertEquals( + Double.parseDouble(cell.getValue().replace('(', ' ') + .replace(')', ' ')), + -Double.parseDouble(testValueCell.getValue()), 0d); } } @@ -158,12 +165,13 @@ public void customFormatting_verifyAllFormulasValidAfterValueChange_Zero() { testValueCell.setValue("0"); for (int row = 0; row < ROW_COUNT; ++row) { for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET, col + COL_OFFSET); Assert.assertFalse(cell.hasInvalidFormulaIndicator()); Assert.assertEquals("===", cell.getValue()); } } - + validateAllColors(); } @@ -176,7 +184,8 @@ public void customFormatting_verifyAllFormulasValidAfterValueChange_String() { testValueCell.setValue("test"); for (int row = 0; row < ROW_COUNT; ++row) { for (int col = 0; col < COL_COUNT; ++col) { - SheetCellElement cell = getSpreadsheet().getCellAt(row + ROW_OFFSET, col + COL_OFFSET); + SheetCellElement cell = getSpreadsheet() + .getCellAt(row + ROW_OFFSET, col + COL_OFFSET); Assert.assertFalse(cell.hasInvalidFormulaIndicator()); Assert.assertEquals("\"test\"", cell.getValue()); } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java index 817edecdd3f..47173bb6406 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CellValueManager.java @@ -244,7 +244,8 @@ protected CellData createCellDataForCell(Cell cell) { } if (formatter instanceof CustomDataFormatter) { - String color = ((CustomDataFormatter)formatter).getCellTextColor(cell); + String color = ((CustomDataFormatter) formatter) + .getCellTextColor(cell); if (color != null) { cellData.textColor = color; } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java index a1c239d01a7..d5ed45480a0 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java @@ -136,7 +136,8 @@ private String formatNumericValueUsingFormatPart(Cell cell, } } - private CellFormatResult formatTextUsingCellFormat(Cell cell, String format) { + private CellFormatResult formatTextUsingCellFormat(Cell cell, + String format) { return CellFormat.getInstance(locale, format).apply(cell); } @@ -145,8 +146,11 @@ private CellFormatResult formatTextUsingCellFormat(Cell cell, String format) { * CellFormat logic, which parses and evaluates the cell's format string * against the cell's current value. * - * @param cell The cell to get the applicable custom formatting text color for. - * @return a CSS color value string, or null if no text color should be applied. + * @param cell + * The cell to get the applicable custom formatting text color + * for. + * @return a CSS color value string, or null if no text color should be + * applied. */ public String getCellTextColor(Cell cell) { try { @@ -156,7 +160,7 @@ public String getCellTextColor(Cell cell) { } CellFormatResult result = formatTextUsingCellFormat(cell, format); - + if (result.textColor == null) { return null; } @@ -165,10 +169,10 @@ public String getCellTextColor(Cell cell) { // rgb(N,N,N) turned out to be the most reliably transmitted string // in testing - String css = "rgb(" + color.getRed() + "," + color.getGreen() + "," + - color.getBlue() + ")"; + String css = "rgb(" + color.getRed() + "," + color.getGreen() + "," + + color.getBlue() + ")"; return css; - } catch(Exception e) { + } catch (Exception e) { return null; } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java index 730416e859c..f496247f9c0 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -1,36 +1,27 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ +/** + * Copyright 2000-2025 Vaadin Ltd. + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See {@literal } for the full + * license. + */ package org.apache.poi.ss.format; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.poi.hssf.util.HSSFColor; -import org.apache.poi.util.CodepointsUtil; -import org.apache.poi.util.LocaleUtil; - -import javax.swing.*; +import static org.apache.poi.ss.format.CellFormatter.quote; import java.awt.*; -import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.*; +import javax.swing.*; -import static org.apache.poi.ss.format.CellFormatter.quote; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.util.CodepointsUtil; +import org.apache.poi.util.LocaleUtil; /* * Note: This file has been overridden by Vaadin to allow proper formatting @@ -54,17 +45,18 @@ Licensed to the Apache Software Foundation (ASF) under one or more * values. *

* Each format part can contain a color, a condition, and will always contain a - * format specification. For example {@code "[Red][>=10]#"} has a color + * format specification. For example {@code "[Red][>=10]#"} has a color * ({@code [Red]}), a condition ({@code >=10}) and a format specification * ({@code #}). *

* This class also contains patterns for matching the subparts of format - * specification. These are used internally, but are made public in case other + * specification. These are used internally, but are made public in case other * code has use for them. */ @SuppressWarnings("RegExpRepeatedSpace") public class CellFormatPart { - private static final Logger LOG = LogManager.getLogger(CellFormatPart.class); + private static final Logger LOG = LogManager + .getLogger(CellFormatPart.class); static final Map NAMED_COLORS; static final List INDEXED_COLORS; @@ -107,84 +99,84 @@ public class CellFormatPart { static { // Build indexed color list based on this table // https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/ - INDEXED_COLORS = List.of( - new Color(0x000000), // Color 1 / black - new Color(0xFFFFFF), // Color 2 / white - new Color(0xFF0000), // Color 3 / red - new Color(0x00FF00), // Color 4 / green - new Color(0x0000FF), // Color 5 / blue - new Color(0xFFFF00), // Color 6 / yellow - new Color(0xFF00FF), // Color 7 / magenta - new Color(0x00FFFF), // Color 8 / cyan - new Color(0x800000), // Color 9 - new Color(0x008000), // Color 10 - new Color(0x000080), // Color 11 - new Color(0x808000), // Color 12 - new Color(0x800080), // Color 13 - new Color(0x008080), // Color 14 - new Color(0xC0C0C0), // Color 15 - new Color(0x808080), // Color 16 - new Color(0x9999FF), // Color 17 - new Color(0x993366), // Color 18 - new Color(0xFFFFCC), // Color 19 - new Color(0xCCFFFF), // Color 20 - new Color(0x660066), // Color 21 - new Color(0xFF8080), // Color 22 - new Color(0x0066CC), // Color 23 - new Color(0xCCCCFF), // Color 24 - new Color(0x000080), // Color 25 - new Color(0xFF00FF), // Color 26 - new Color(0xFFFF00), // Color 27 - new Color(0x00FFFF), // Color 28 - new Color(0x800080), // Color 29 - new Color(0x800000), // Color 30 - new Color(0x008080), // Color 31 - new Color(0x0000FF), // Color 32 - new Color(0x00CCFF), // Color 33 - new Color(0xCCFFFF), // Color 34 - new Color(0xCCFFCC), // Color 35 - new Color(0xFFFF99), // Color 36 - new Color(0x99CCFF), // Color 37 - new Color(0xFF99CC), // Color 38 - new Color(0xCC99FF), // Color 39 - new Color(0xFFCC99), // Color 40 - new Color(0x3366FF), // Color 41 - new Color(0x33CCCC), // Color 42 - new Color(0x99CC00), // Color 43 - new Color(0xFFCC00), // Color 44 - new Color(0xFF9900), // Color 45 - new Color(0xFF6600), // Color 46 - new Color(0x666699), // Color 47 - new Color(0x969696), // Color 48 - new Color(0x003366), // Color 49 - new Color(0x339966), // Color 50 - new Color(0x003300), // Color 51 - new Color(0x333300), // Color 52 - new Color(0x993300), // Color 53 - new Color(0x993366), // Color 54 - new Color(0x333399), // Color 55 - new Color(0x333333) // Color 56 + INDEXED_COLORS = List.of(new Color(0x000000), // Color 1 / black + new Color(0xFFFFFF), // Color 2 / white + new Color(0xFF0000), // Color 3 / red + new Color(0x00FF00), // Color 4 / green + new Color(0x0000FF), // Color 5 / blue + new Color(0xFFFF00), // Color 6 / yellow + new Color(0xFF00FF), // Color 7 / magenta + new Color(0x00FFFF), // Color 8 / cyan + new Color(0x800000), // Color 9 + new Color(0x008000), // Color 10 + new Color(0x000080), // Color 11 + new Color(0x808000), // Color 12 + new Color(0x800080), // Color 13 + new Color(0x008080), // Color 14 + new Color(0xC0C0C0), // Color 15 + new Color(0x808080), // Color 16 + new Color(0x9999FF), // Color 17 + new Color(0x993366), // Color 18 + new Color(0xFFFFCC), // Color 19 + new Color(0xCCFFFF), // Color 20 + new Color(0x660066), // Color 21 + new Color(0xFF8080), // Color 22 + new Color(0x0066CC), // Color 23 + new Color(0xCCCCFF), // Color 24 + new Color(0x000080), // Color 25 + new Color(0xFF00FF), // Color 26 + new Color(0xFFFF00), // Color 27 + new Color(0x00FFFF), // Color 28 + new Color(0x800080), // Color 29 + new Color(0x800000), // Color 30 + new Color(0x008080), // Color 31 + new Color(0x0000FF), // Color 32 + new Color(0x00CCFF), // Color 33 + new Color(0xCCFFFF), // Color 34 + new Color(0xCCFFCC), // Color 35 + new Color(0xFFFF99), // Color 36 + new Color(0x99CCFF), // Color 37 + new Color(0xFF99CC), // Color 38 + new Color(0xCC99FF), // Color 39 + new Color(0xFFCC99), // Color 40 + new Color(0x3366FF), // Color 41 + new Color(0x33CCCC), // Color 42 + new Color(0x99CC00), // Color 43 + new Color(0xFFCC00), // Color 44 + new Color(0xFF9900), // Color 45 + new Color(0xFF6600), // Color 46 + new Color(0x666699), // Color 47 + new Color(0x969696), // Color 48 + new Color(0x003366), // Color 49 + new Color(0x339966), // Color 50 + new Color(0x003300), // Color 51 + new Color(0x333300), // Color 52 + new Color(0x993300), // Color 53 + new Color(0x993366), // Color 54 + new Color(0x333399), // Color 55 + new Color(0x333333) // Color 56 ); - + // Build named color list based on HSSFColorPredefined, just as // Apache POI does it originally. This gives us a wider range // of acceptable colors, but also puts out outside the acceptable // color range of Excel. - NAMED_COLORS = new TreeMap<>( - String.CASE_INSENSITIVE_ORDER); - - for (HSSFColor.HSSFColorPredefined color : HSSFColor.HSSFColorPredefined.values()) { - String name = color.name().toLowerCase(); - short[] rgb = color.getTriplet(); - Color c = new Color(rgb[0], rgb[1], rgb[2]); - NAMED_COLORS.put(name, c); - if (name.indexOf("_percent") > 0) { - NAMED_COLORS.put(name.replace("_percent", "%") - .replaceAll("\\_", " "), c); - } - if (name.indexOf('_') > 0) { - NAMED_COLORS.put(name.replaceAll("\\_", " "), c); - } + NAMED_COLORS = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + for (HSSFColor.HSSFColorPredefined color : HSSFColor.HSSFColorPredefined + .values()) { + String name = color.name().toLowerCase(); + short[] rgb = color.getTriplet(); + Color c = new Color(rgb[0], rgb[1], rgb[2]); + NAMED_COLORS.put(name, c); + if (name.indexOf("_percent") > 0) { + NAMED_COLORS.put( + name.replace("_percent", "%").replaceAll("\\_", " "), + c); + } + if (name.indexOf('_') > 0) { + NAMED_COLORS.put(name.replaceAll("\\_", " "), c); + } } // Replace the standard colors with standard values @@ -199,38 +191,41 @@ public class CellFormatPart { NAMED_COLORS.put("cyan", INDEXED_COLORS.get(7)); // A condition specification - String condition = "([<>=]=?|!=|<>) # The operator\n" + - " \\s*(-?([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]*))\\s* # The constant to test against\n"; + String condition = "([<>=]=?|!=|<>) # The operator\n" + + " \\s*(-?([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]*))\\s* # The constant to test against\n"; // A currency symbol / string, in a specific locale String currency = "(\\[\\$.{0,3}(-[0-9a-f]{3,4})?])"; // A number specification - // Note: careful that in something like ##, that the trailing comma is not caught up in the integer part + // Note: careful that in something like ##, that the trailing comma is + // not caught up in the integer part // A part of a specification - //noinspection RegExpRedundantEscape - String part = "\\\\. # Quoted single character\n" + - "|\"([^\\\\\"]|\\\\.)*\" # Quoted string of characters (handles escaped quotes like \\\") \n" + - "|"+currency+" # Currency symbol in a given locale\n" + - "|_. # Space as wide as a given character\n" + - "|\\*. # Repeating fill character\n" + - "|@ # Text: cell text\n" + - "|([0?\\#][0?\\#,]*) # Number: digit + other digits and commas\n" + - "|e[-+] # Number: Scientific: Exponent\n" + - "|m{1,5} # Date: month or minute spec\n" + - "|d{1,4} # Date: day/date spec\n" + - "|y{2,4} # Date: year spec\n" + - "|h{1,2} # Date: hour spec\n" + - "|s{1,2} # Date: second spec\n" + - "|am?/pm? # Date: am/pm spec\n" + - "|\\[h{1,2}] # Elapsed time: hour spec\n" + - "|\\[m{1,2}] # Elapsed time: minute spec\n" + - "|\\[s{1,2}] # Elapsed time: second spec\n" + - "|[^;] # A character\n" + ""; + // noinspection RegExpRedundantEscape + String part = "\\\\. # Quoted single character\n" + + "|\"([^\\\\\"]|\\\\.)*\" # Quoted string of characters (handles escaped quotes like \\\") \n" + + "|" + currency + + " # Currency symbol in a given locale\n" + + "|_. # Space as wide as a given character\n" + + "|\\*. # Repeating fill character\n" + + "|@ # Text: cell text\n" + + "|([0?\\#][0?\\#,]*) # Number: digit + other digits and commas\n" + + "|e[-+] # Number: Scientific: Exponent\n" + + "|m{1,5} # Date: month or minute spec\n" + + "|d{1,4} # Date: day/date spec\n" + + "|y{2,4} # Date: year spec\n" + + "|h{1,2} # Date: hour spec\n" + + "|s{1,2} # Date: second spec\n" + + "|am?/pm? # Date: am/pm spec\n" + + "|\\[h{1,2}] # Elapsed time: hour spec\n" + + "|\\[m{1,2}] # Elapsed time: minute spec\n" + + "|\\[s{1,2}] # Elapsed time: second spec\n" + + "|[^;] # A character\n" + ""; // Build the color code matching expression. - // We should match any named color in the set as well as a string in the form + // We should match any named color in the set as well as a string in the + // form // of "Color 8" or "Color 15". String color = "\\[("; for (String key : NAMED_COLORS.keySet()) { @@ -240,12 +235,13 @@ public class CellFormatPart { // Match the indexed color table color += "color\\ [0-9]+)\\]"; - String format = "(?:" + color + ")? # Text color\n" + - "(?:\\[" + condition + "])? # Condition\n" + - // see https://msdn.microsoft.com/en-ca/goglobal/bb964664.aspx and https://bz.apache.org/ooo/show_bug.cgi?id=70003 + String format = "(?:" + color + ")? # Text color\n" + + "(?:\\[" + condition + "])? # Condition\n" + + // see https://msdn.microsoft.com/en-ca/goglobal/bb964664.aspx + // and https://bz.apache.org/ooo/show_bug.cgi?id=70003 // we ignore these for now though - "(?:\\[\\$-[0-9a-fA-F]+])? # Optional locale id, ignored currently\n" + - "((?:" + part + ")+) # Format spec\n"; + "(?:\\[\\$-[0-9a-fA-F]+])? # Optional locale id, ignored currently\n" + + "((?:" + part + ")+) # Format spec\n"; int flags = Pattern.COMMENTS | Pattern.CASE_INSENSITIVE; COLOR_PAT = Pattern.compile(color, flags); @@ -264,14 +260,14 @@ public class CellFormatPart { SPECIFICATION_GROUP = findGroup(FORMAT_PAT, "[Blue][>1]\\a ?", "\\a ?"); // Once patterns have been compiled, add indexed colors to - // NAMED_COLORS so they can be easily picked up by an unmodified + // NAMED_COLORS so they can be easily picked up by an unmodified // getColor() implementation for (int i = 0; i < INDEXED_COLORS.size(); ++i) { NAMED_COLORS.put("color " + (i + 1), INDEXED_COLORS.get(i)); } // NOTE: the INDEXED_COLORS list is retained for future use, even - // though it is not currently utilized outside the static - // initialization logic. + // though it is not currently utilized outside the static + // initialization logic. } interface PartHandler { @@ -282,7 +278,8 @@ String handlePart(Matcher m, String part, CellFormatType type, /** * Create an object to represent a format part. * - * @param desc The string to parse. + * @param desc + * The string to parse. */ public CellFormatPart(String desc) { this(LocaleUtil.getUserLocale(), desc); @@ -291,14 +288,16 @@ public CellFormatPart(String desc) { /** * Create an object to represent a format part. * - * @param locale The locale to use. - * @param desc The string to parse. + * @param locale + * The locale to use. + * @param desc + * The string to parse. */ public CellFormatPart(Locale locale, String desc) { Matcher m = FORMAT_PAT.matcher(desc); if (!m.matches()) { - throw new IllegalArgumentException("Unrecognized format: " + quote( - desc)); + throw new IllegalArgumentException( + "Unrecognized format: " + quote(desc)); } color = getColor(m); condition = getCondition(m); @@ -309,10 +308,11 @@ public CellFormatPart(Locale locale, String desc) { /** * Returns {@code true} if this format part applies to the given value. If * the value is a number and this is part has a condition, returns - * {@code true} only if the number passes the condition. Otherwise, this + * {@code true} only if the number passes the condition. Otherwise, this * always return {@code true}. * - * @param valueObject The value to evaluate. + * @param valueObject + * The value to evaluate. * * @return {@code true} if this format part applies to the given value. */ @@ -331,20 +331,23 @@ public boolean applies(Object valueObject) { * Returns the number of the first group that is the same as the marker * string. Starts from group 1. * - * @param pat The pattern to use. - * @param str The string to match against the pattern. - * @param marker The marker value to find the group of. + * @param pat + * The pattern to use. + * @param str + * The string to match against the pattern. + * @param marker + * The marker value to find the group of. * * @return The matching group number. * - * @throws IllegalArgumentException No group matches the marker. + * @throws IllegalArgumentException + * No group matches the marker. */ private static int findGroup(Pattern pat, String str, String marker) { Matcher m = pat.matcher(str); if (!m.find()) - throw new IllegalArgumentException( - "Pattern \"" + pat.pattern() + "\" doesn't match \"" + str + - "\""); + throw new IllegalArgumentException("Pattern \"" + pat.pattern() + + "\" doesn't match \"" + str + "\""); for (int i = 1; i <= m.groupCount(); i++) { String grp = m.group(i); if (grp != null && grp.equals(marker)) @@ -358,7 +361,8 @@ private static int findGroup(Pattern pat, String str, String marker) { * Returns the color specification from the matcher, or {@code null} if * there is none. * - * @param m The matcher for the format part. + * @param m + * The matcher for the format part. * * @return The color specification or {@code null}. */ @@ -380,7 +384,8 @@ private static Color getColor(Matcher m) { * Returns the condition specification from the matcher, or {@code null} if * there is none. * - * @param m The matcher for the format part. + * @param m + * The matcher for the format part. * * @return The condition specification or {@code null}. */ @@ -388,15 +393,17 @@ private CellFormatCondition getCondition(Matcher m) { String mdesc = m.group(CONDITION_OPERATOR_GROUP); if (mdesc == null || mdesc.length() == 0) return null; - return CellFormatCondition.getInstance(m.group( - CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP)); + return CellFormatCondition.getInstance( + m.group(CONDITION_OPERATOR_GROUP), + m.group(CONDITION_VALUE_GROUP)); } /** * Returns the CellFormatType object implied by the format specification for * the format part. * - * @param matcher The matcher for the format part. + * @param matcher + * The matcher for the format part. * * @return The CellFormatType. */ @@ -409,7 +416,8 @@ private CellFormatType getCellFormatType(Matcher matcher) { * Returns the formatter object implied by the format specification for the * format part. * - * @param matcher The matcher for the format part. + * @param matcher + * The matcher for the format part. * * @return The formatter. */ @@ -426,9 +434,11 @@ private CellFormatter getFormatter(Locale locale, Matcher matcher) { currencyRepl = "$"; } else if (!currencyPart.contains("-")) { // Accounting formats such as USD [$USD] - currencyRepl = currencyPart.substring(2, currencyPart.indexOf("]")); + currencyRepl = currencyPart.substring(2, + currencyPart.indexOf("]")); } else { - currencyRepl = currencyPart.substring(2, currencyPart.lastIndexOf('-')); + currencyRepl = currencyPart.substring(2, + currencyPart.lastIndexOf('-')); } fdesc = fdesc.replace(currencyPart, currencyRepl); } @@ -440,7 +450,8 @@ private CellFormatter getFormatter(Locale locale, Matcher matcher) { /** * Returns the type of format. * - * @param fdesc The format specification + * @param fdesc + * The format specification * * @return The type of format. */ @@ -491,8 +502,9 @@ private CellFormatType formatType(String fdesc) { return CellFormatType.NUMBER; } // Something else inside [] which isn't supported! - throw new IllegalArgumentException("Unsupported [] format block '" + - repl + "' in '" + fdesc + "' with c2: " + c2); + throw new IllegalArgumentException( + "Unsupported [] format block '" + repl + "' in '" + + fdesc + "' with c2: " + c2); case "#": case "?": return CellFormatType.NUMBER; @@ -510,11 +522,13 @@ private CellFormatType formatType(String fdesc) { /** * Returns a version of the original string that has any special characters - * quoted (or escaped) as appropriate for the cell format type. The format + * quoted (or escaped) as appropriate for the cell format type. The format * type object is queried to see what is special. * - * @param repl The original string. - * @param type The format type representation object. + * @param repl + * The original string. + * @param type + * The format type representation object. * * @return A version of the string with any special characters replaced. * @@ -522,7 +536,8 @@ private CellFormatType formatType(String fdesc) { */ static String quoteSpecial(String repl, CellFormatType type) { StringBuilder sb = new StringBuilder(); - PrimitiveIterator.OfInt codePoints = CodepointsUtil.primitiveIterator(repl); + PrimitiveIterator.OfInt codePoints = CodepointsUtil + .primitiveIterator(repl); int codepoint; while (codePoints.hasNext()) { @@ -544,10 +559,11 @@ static String quoteSpecial(String repl, CellFormatType type) { } /** - * Apply this format part to the given value. This returns a {@link - * CellFormatResult} object with the results. + * Apply this format part to the given value. This returns a + * {@link CellFormatResult} object with the results. * - * @param value The value to apply this format part to. + * @param value + * The value to apply this format part to. * * @return A {@link CellFormatResult} object containing the results of * applying the format to the value. @@ -570,8 +586,10 @@ public CellFormatResult apply(Object value) { * Apply this format part to the given value, applying the result to the * given label. * - * @param label The label - * @param value The value to apply this format part to. + * @param label + * The label + * @param value + * The value to apply this format part to. * * @return {@code true} if the */ @@ -606,19 +624,19 @@ boolean hasCondition() { public static StringBuffer parseFormat(String fdesc, CellFormatType type, PartHandler partHandler) { - // Quoting is very awkward. In the Java classes, quoting is done + // Quoting is very awkward. In the Java classes, quoting is done // between ' chars, with '' meaning a single ' char. The problem is that - // in Excel, it is legal to have two adjacent escaped strings. For - // example, consider the Excel format "\a\b#". The naive (and easy) - // translation into Java DecimalFormat is "'a''b'#". For the number 17, + // in Excel, it is legal to have two adjacent escaped strings. For + // example, consider the Excel format "\a\b#". The naive (and easy) + // translation into Java DecimalFormat is "'a''b'#". For the number 17, // in Excel you would get "ab17", but in Java it would be "a'b17" -- the - // '' is in the middle of the quoted string in Java. So the trick we + // '' is in the middle of the quoted string in Java. So the trick we // use is this: When we encounter a ' char in the Excel format, we - // output a \u0000 char into the string. Now we know that any '' in the - // output is the result of two adjacent escaped strings. So after the + // output a \u0000 char into the string. Now we know that any '' in the + // output is the result of two adjacent escaped strings. So after the // main loop, we have to do two passes: One to eliminate any '' // sequences, to make "'a''b'" become "'ab'", and another to replace any - // \u0000 with '' to mean a quote char. Oy. + // \u0000 with '' to mean a quote char. Oy. // // For formats that don't use "'" we don't do any of this Matcher m = SPECIFICATION_PAT.matcher(fdesc); @@ -630,8 +648,8 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, if (repl == null) { switch (part.charAt(0)) { case '\"': - repl = quoteSpecial(part.substring(1, - part.length() - 1), type); + repl = quoteSpecial( + part.substring(1, part.length() - 1), type); break; case '\\': repl = quoteSpecial(part.substring(1), type); @@ -639,7 +657,8 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, case '_': repl = " "; break; - case '*': //!! We don't do this for real, we just put in 3 of them + case '*': // !! We don't do this for real, we just put in 3 + // of them repl = expandChar(part); break; default: @@ -653,7 +672,8 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, m.appendTail(fmt); if (type.isSpecial('\'')) { - // Now the next pass for quoted characters: Remove '' chars, making "'a''b'" into "'ab'" + // Now the next pass for quoted characters: Remove '' chars, making + // "'a''b'" into "'ab'" int pos = 0; while ((pos = fmt.indexOf("''", pos)) >= 0) { fmt.delete(pos, pos + 2); @@ -679,18 +699,21 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, /** * Expands a character. This is only partly done, because we don't have the - * correct info. In Excel, this would be expanded to fill the rest of the + * correct info. In Excel, this would be expanded to fill the rest of the * cell, but we don't know, in general, what the "rest of the cell" is. * - * @param part The character to be repeated is the second character in this - * string. + * @param part + * The character to be repeated is the second character in this + * string. * * @return The character repeated three times. */ static String expandChar(String part) { List codePoints = new ArrayList<>(); CodepointsUtil.iteratorFor(part).forEachRemaining(codePoints::add); - if (codePoints.size() < 2) throw new IllegalArgumentException("Expected part string to have at least 2 chars"); + if (codePoints.size() < 2) + throw new IllegalArgumentException( + "Expected part string to have at least 2 chars"); String ch = codePoints.get(1); return ch + ch + ch; } @@ -699,8 +722,10 @@ static String expandChar(String part) { * Returns the string from the group, or {@code ""} if the group is * {@code null}. * - * @param m The matcher. - * @param g The group number. + * @param m + * The matcher. + * @param g + * The group number. * * @return The group or {@code ""}. */ From 8cbec3218b0af97750a021cac514a0ac79ac24b8 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Tue, 25 Feb 2025 15:16:02 +0200 Subject: [PATCH 3/8] attempt to fix some of the formatting --- .../spreadsheet/test/CustomFormattingIT.java | 31 ++++++++++--------- .../apache/poi/ss/format/CellFormatPart.java | 24 +++++++++++++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java index 57e4f685f7d..bfa509f4864 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java @@ -25,7 +25,8 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { private static final int COL_COUNT = 8; // The 8 basic named colors, in order in the document, as CSS strings - private static final String[] NAMED_COLOR_CSS = { "rgba(0, 0, 0, 1)", // black + private static final String[] NAMED_COLOR_CSS = { // new line here + "rgba(0, 0, 0, 1)", // black "rgba(0, 0, 255, 1)", // blue "rgba(0, 255, 255, 1)", // cyan "rgba(0, 255, 0, 1)", // green @@ -40,22 +41,24 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { static { // Color values in hex, taken from the modified CellFormatPart.java // in vaadin-spreadsheet-flow. These are the 56 indexed Excel colors - final int[] rgb_hex = { 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, - 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0x800000, 0x008000, - 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080, - 0x9999FF, 0x993366, 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, - 0x0066CC, 0xCCCCFF, 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, - 0x800080, 0x800000, 0x008080, 0x0000FF, 0x00CCFF, 0xCCFFFF, - 0xCCFFCC, 0xFFFF99, 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, - 0x3366FF, 0x33CCCC, 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, - 0x666699, 0x969696, 0x003366, 0x339966, 0x003300, 0x333300, - 0x993300, 0x993366, 0x333399, 0x333333 }; + final int[] rgb_hex = { // --- line change here --- + 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, + 0xFF00FF, 0x00FFFF, 0x800000, 0x008000, 0x000080, 0x808000, + 0x800080, 0x008080, 0xC0C0C0, 0x808080, 0x9999FF, 0x993366, + 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, 0x0066CC, 0xCCCCFF, + 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, 0x800080, 0x800000, + 0x008080, 0x0000FF, 0x00CCFF, 0xCCFFFF, 0xCCFFCC, 0xFFFF99, + 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, 0x3366FF, 0x33CCCC, + 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, 0x666699, 0x969696, + 0x003366, 0x339966, 0x003300, 0x333300, 0x993300, 0x993366, + 0x333399, 0x333333 }; // Convert table to CSS strings for (int i = 0; i < 56; ++i) { - INDEXED_COLOR_CSS[i] = "rgba(" + ((rgb_hex[i] >>> 16) & 0xff) + ", " - + ((rgb_hex[i] >>> 8) & 0xff) + ", " + (rgb_hex[i] & 0xff) - + ", 1)"; + INDEXED_COLOR_CSS[i] = "rgba(" + // results returnd as rgba + ((rgb_hex[i] >>> 16) & 0xff) + ", " + // red component + ((rgb_hex[i] >>> 8) & 0xff) + ", " + // green component + (rgb_hex[i] & 0xff) + ", 1)"; // blue component and solid alpha } } diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java index f496247f9c0..f72d30eeb2e 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -8,6 +8,27 @@ */ package org.apache.poi.ss.format; +/* + * The Vaadin CI pipeline requires Vaadin's license header to be first in the file. + * While this file is modified, most of it was part of Apache POI, which is licensed + * under the Apache License, Version 2.0. + */ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + See the License for the specific language governing permissions and + limitations under the License. + ==================================================================== */ + import static org.apache.poi.ss.format.CellFormatter.quote; import java.awt.*; @@ -99,7 +120,8 @@ public class CellFormatPart { static { // Build indexed color list based on this table // https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/ - INDEXED_COLORS = List.of(new Color(0x000000), // Color 1 / black + INDEXED_COLORS = List.of( // new line + new Color(0x000000), // Color 1 / black new Color(0xFFFFFF), // Color 2 / white new Color(0xFF0000), // Color 3 / red new Color(0x00FF00), // Color 4 / green From 3ad25208595adc20cf2bbb834f4ed7f65b3f712c Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Tue, 25 Feb 2025 15:21:05 +0200 Subject: [PATCH 4/8] appease the formatting bot --- .../component/spreadsheet/test/CustomFormattingIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java index bfa509f4864..b9487d4cc8e 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow-integration-tests/src/test/java/com/vaadin/flow/component/spreadsheet/test/CustomFormattingIT.java @@ -41,7 +41,7 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { static { // Color values in hex, taken from the modified CellFormatPart.java // in vaadin-spreadsheet-flow. These are the 56 indexed Excel colors - final int[] rgb_hex = { // --- line change here --- + final int[] rgb_hex = { // new line here 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0x800000, 0x008000, 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080, 0x9999FF, 0x993366, @@ -56,9 +56,9 @@ public class CustomFormattingIT extends AbstractSpreadsheetIT { // Convert table to CSS strings for (int i = 0; i < 56; ++i) { INDEXED_COLOR_CSS[i] = "rgba(" + // results returnd as rgba - ((rgb_hex[i] >>> 16) & 0xff) + ", " + // red component - ((rgb_hex[i] >>> 8) & 0xff) + ", " + // green component - (rgb_hex[i] & 0xff) + ", 1)"; // blue component and solid alpha + ((rgb_hex[i] >>> 16) & 0xff) + ", " + // red component + ((rgb_hex[i] >>> 8) & 0xff) + ", " + // green component + (rgb_hex[i] & 0xff) + ", 1)"; // blue component and opaque } } From e03d9451e0533ffe6635ac4777db1cdda0a4f619 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Tue, 25 Feb 2025 15:31:57 +0200 Subject: [PATCH 5/8] Unmangle Apache's code --- pom.xml | 2 + .../apache/poi/ss/format/CellFormatPart.java | 391 ++++++++---------- 2 files changed, 174 insertions(+), 219 deletions(-) diff --git a/pom.xml b/pom.xml index e1030444e3f..ab08b483ad6 100644 --- a/pom.xml +++ b/pom.xml @@ -547,6 +547,8 @@ **/com/google/gwt/**/*.* + + **/org/apache/poi/**/*.* diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java index f72d30eeb2e..730416e859c 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -1,19 +1,3 @@ -/** - * Copyright 2000-2025 Vaadin Ltd. - * - * This program is available under Vaadin Commercial License and Service Terms. - * - * See {@literal } for the full - * license. - */ -package org.apache.poi.ss.format; - -/* - * The Vaadin CI pipeline requires Vaadin's license header to be first in the file. - * While this file is modified, most of it was part of Apache POI, which is licensed - * under the Apache License, Version 2.0. - */ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -21,28 +5,32 @@ Licensed to the Apache Software Foundation (ASF) under one or more The ASF licenses this file to You 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. See the License for the specific language governing permissions and limitations under the License. - ==================================================================== */ +==================================================================== */ +package org.apache.poi.ss.format; -import static org.apache.poi.ss.format.CellFormatter.quote; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.util.CodepointsUtil; +import org.apache.poi.util.LocaleUtil; + +import javax.swing.*; import java.awt.*; +import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.*; -import javax.swing.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.poi.hssf.util.HSSFColor; -import org.apache.poi.util.CodepointsUtil; -import org.apache.poi.util.LocaleUtil; +import static org.apache.poi.ss.format.CellFormatter.quote; /* * Note: This file has been overridden by Vaadin to allow proper formatting @@ -66,18 +54,17 @@ Licensed to the Apache Software Foundation (ASF) under one or more * values. *

* Each format part can contain a color, a condition, and will always contain a - * format specification. For example {@code "[Red][>=10]#"} has a color + * format specification. For example {@code "[Red][>=10]#"} has a color * ({@code [Red]}), a condition ({@code >=10}) and a format specification * ({@code #}). *

* This class also contains patterns for matching the subparts of format - * specification. These are used internally, but are made public in case other + * specification. These are used internally, but are made public in case other * code has use for them. */ @SuppressWarnings("RegExpRepeatedSpace") public class CellFormatPart { - private static final Logger LOG = LogManager - .getLogger(CellFormatPart.class); + private static final Logger LOG = LogManager.getLogger(CellFormatPart.class); static final Map NAMED_COLORS; static final List INDEXED_COLORS; @@ -120,85 +107,84 @@ public class CellFormatPart { static { // Build indexed color list based on this table // https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/ - INDEXED_COLORS = List.of( // new line - new Color(0x000000), // Color 1 / black - new Color(0xFFFFFF), // Color 2 / white - new Color(0xFF0000), // Color 3 / red - new Color(0x00FF00), // Color 4 / green - new Color(0x0000FF), // Color 5 / blue - new Color(0xFFFF00), // Color 6 / yellow - new Color(0xFF00FF), // Color 7 / magenta - new Color(0x00FFFF), // Color 8 / cyan - new Color(0x800000), // Color 9 - new Color(0x008000), // Color 10 - new Color(0x000080), // Color 11 - new Color(0x808000), // Color 12 - new Color(0x800080), // Color 13 - new Color(0x008080), // Color 14 - new Color(0xC0C0C0), // Color 15 - new Color(0x808080), // Color 16 - new Color(0x9999FF), // Color 17 - new Color(0x993366), // Color 18 - new Color(0xFFFFCC), // Color 19 - new Color(0xCCFFFF), // Color 20 - new Color(0x660066), // Color 21 - new Color(0xFF8080), // Color 22 - new Color(0x0066CC), // Color 23 - new Color(0xCCCCFF), // Color 24 - new Color(0x000080), // Color 25 - new Color(0xFF00FF), // Color 26 - new Color(0xFFFF00), // Color 27 - new Color(0x00FFFF), // Color 28 - new Color(0x800080), // Color 29 - new Color(0x800000), // Color 30 - new Color(0x008080), // Color 31 - new Color(0x0000FF), // Color 32 - new Color(0x00CCFF), // Color 33 - new Color(0xCCFFFF), // Color 34 - new Color(0xCCFFCC), // Color 35 - new Color(0xFFFF99), // Color 36 - new Color(0x99CCFF), // Color 37 - new Color(0xFF99CC), // Color 38 - new Color(0xCC99FF), // Color 39 - new Color(0xFFCC99), // Color 40 - new Color(0x3366FF), // Color 41 - new Color(0x33CCCC), // Color 42 - new Color(0x99CC00), // Color 43 - new Color(0xFFCC00), // Color 44 - new Color(0xFF9900), // Color 45 - new Color(0xFF6600), // Color 46 - new Color(0x666699), // Color 47 - new Color(0x969696), // Color 48 - new Color(0x003366), // Color 49 - new Color(0x339966), // Color 50 - new Color(0x003300), // Color 51 - new Color(0x333300), // Color 52 - new Color(0x993300), // Color 53 - new Color(0x993366), // Color 54 - new Color(0x333399), // Color 55 - new Color(0x333333) // Color 56 + INDEXED_COLORS = List.of( + new Color(0x000000), // Color 1 / black + new Color(0xFFFFFF), // Color 2 / white + new Color(0xFF0000), // Color 3 / red + new Color(0x00FF00), // Color 4 / green + new Color(0x0000FF), // Color 5 / blue + new Color(0xFFFF00), // Color 6 / yellow + new Color(0xFF00FF), // Color 7 / magenta + new Color(0x00FFFF), // Color 8 / cyan + new Color(0x800000), // Color 9 + new Color(0x008000), // Color 10 + new Color(0x000080), // Color 11 + new Color(0x808000), // Color 12 + new Color(0x800080), // Color 13 + new Color(0x008080), // Color 14 + new Color(0xC0C0C0), // Color 15 + new Color(0x808080), // Color 16 + new Color(0x9999FF), // Color 17 + new Color(0x993366), // Color 18 + new Color(0xFFFFCC), // Color 19 + new Color(0xCCFFFF), // Color 20 + new Color(0x660066), // Color 21 + new Color(0xFF8080), // Color 22 + new Color(0x0066CC), // Color 23 + new Color(0xCCCCFF), // Color 24 + new Color(0x000080), // Color 25 + new Color(0xFF00FF), // Color 26 + new Color(0xFFFF00), // Color 27 + new Color(0x00FFFF), // Color 28 + new Color(0x800080), // Color 29 + new Color(0x800000), // Color 30 + new Color(0x008080), // Color 31 + new Color(0x0000FF), // Color 32 + new Color(0x00CCFF), // Color 33 + new Color(0xCCFFFF), // Color 34 + new Color(0xCCFFCC), // Color 35 + new Color(0xFFFF99), // Color 36 + new Color(0x99CCFF), // Color 37 + new Color(0xFF99CC), // Color 38 + new Color(0xCC99FF), // Color 39 + new Color(0xFFCC99), // Color 40 + new Color(0x3366FF), // Color 41 + new Color(0x33CCCC), // Color 42 + new Color(0x99CC00), // Color 43 + new Color(0xFFCC00), // Color 44 + new Color(0xFF9900), // Color 45 + new Color(0xFF6600), // Color 46 + new Color(0x666699), // Color 47 + new Color(0x969696), // Color 48 + new Color(0x003366), // Color 49 + new Color(0x339966), // Color 50 + new Color(0x003300), // Color 51 + new Color(0x333300), // Color 52 + new Color(0x993300), // Color 53 + new Color(0x993366), // Color 54 + new Color(0x333399), // Color 55 + new Color(0x333333) // Color 56 ); - + // Build named color list based on HSSFColorPredefined, just as // Apache POI does it originally. This gives us a wider range // of acceptable colors, but also puts out outside the acceptable // color range of Excel. - NAMED_COLORS = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - - for (HSSFColor.HSSFColorPredefined color : HSSFColor.HSSFColorPredefined - .values()) { - String name = color.name().toLowerCase(); - short[] rgb = color.getTriplet(); - Color c = new Color(rgb[0], rgb[1], rgb[2]); - NAMED_COLORS.put(name, c); - if (name.indexOf("_percent") > 0) { - NAMED_COLORS.put( - name.replace("_percent", "%").replaceAll("\\_", " "), - c); - } - if (name.indexOf('_') > 0) { - NAMED_COLORS.put(name.replaceAll("\\_", " "), c); - } + NAMED_COLORS = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + + for (HSSFColor.HSSFColorPredefined color : HSSFColor.HSSFColorPredefined.values()) { + String name = color.name().toLowerCase(); + short[] rgb = color.getTriplet(); + Color c = new Color(rgb[0], rgb[1], rgb[2]); + NAMED_COLORS.put(name, c); + if (name.indexOf("_percent") > 0) { + NAMED_COLORS.put(name.replace("_percent", "%") + .replaceAll("\\_", " "), c); + } + if (name.indexOf('_') > 0) { + NAMED_COLORS.put(name.replaceAll("\\_", " "), c); + } } // Replace the standard colors with standard values @@ -213,41 +199,38 @@ public class CellFormatPart { NAMED_COLORS.put("cyan", INDEXED_COLORS.get(7)); // A condition specification - String condition = "([<>=]=?|!=|<>) # The operator\n" - + " \\s*(-?([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]*))\\s* # The constant to test against\n"; + String condition = "([<>=]=?|!=|<>) # The operator\n" + + " \\s*(-?([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]*))\\s* # The constant to test against\n"; // A currency symbol / string, in a specific locale String currency = "(\\[\\$.{0,3}(-[0-9a-f]{3,4})?])"; // A number specification - // Note: careful that in something like ##, that the trailing comma is - // not caught up in the integer part + // Note: careful that in something like ##, that the trailing comma is not caught up in the integer part // A part of a specification - // noinspection RegExpRedundantEscape - String part = "\\\\. # Quoted single character\n" - + "|\"([^\\\\\"]|\\\\.)*\" # Quoted string of characters (handles escaped quotes like \\\") \n" - + "|" + currency - + " # Currency symbol in a given locale\n" - + "|_. # Space as wide as a given character\n" - + "|\\*. # Repeating fill character\n" - + "|@ # Text: cell text\n" - + "|([0?\\#][0?\\#,]*) # Number: digit + other digits and commas\n" - + "|e[-+] # Number: Scientific: Exponent\n" - + "|m{1,5} # Date: month or minute spec\n" - + "|d{1,4} # Date: day/date spec\n" - + "|y{2,4} # Date: year spec\n" - + "|h{1,2} # Date: hour spec\n" - + "|s{1,2} # Date: second spec\n" - + "|am?/pm? # Date: am/pm spec\n" - + "|\\[h{1,2}] # Elapsed time: hour spec\n" - + "|\\[m{1,2}] # Elapsed time: minute spec\n" - + "|\\[s{1,2}] # Elapsed time: second spec\n" - + "|[^;] # A character\n" + ""; + //noinspection RegExpRedundantEscape + String part = "\\\\. # Quoted single character\n" + + "|\"([^\\\\\"]|\\\\.)*\" # Quoted string of characters (handles escaped quotes like \\\") \n" + + "|"+currency+" # Currency symbol in a given locale\n" + + "|_. # Space as wide as a given character\n" + + "|\\*. # Repeating fill character\n" + + "|@ # Text: cell text\n" + + "|([0?\\#][0?\\#,]*) # Number: digit + other digits and commas\n" + + "|e[-+] # Number: Scientific: Exponent\n" + + "|m{1,5} # Date: month or minute spec\n" + + "|d{1,4} # Date: day/date spec\n" + + "|y{2,4} # Date: year spec\n" + + "|h{1,2} # Date: hour spec\n" + + "|s{1,2} # Date: second spec\n" + + "|am?/pm? # Date: am/pm spec\n" + + "|\\[h{1,2}] # Elapsed time: hour spec\n" + + "|\\[m{1,2}] # Elapsed time: minute spec\n" + + "|\\[s{1,2}] # Elapsed time: second spec\n" + + "|[^;] # A character\n" + ""; // Build the color code matching expression. - // We should match any named color in the set as well as a string in the - // form + // We should match any named color in the set as well as a string in the form // of "Color 8" or "Color 15". String color = "\\[("; for (String key : NAMED_COLORS.keySet()) { @@ -257,13 +240,12 @@ public class CellFormatPart { // Match the indexed color table color += "color\\ [0-9]+)\\]"; - String format = "(?:" + color + ")? # Text color\n" - + "(?:\\[" + condition + "])? # Condition\n" + - // see https://msdn.microsoft.com/en-ca/goglobal/bb964664.aspx - // and https://bz.apache.org/ooo/show_bug.cgi?id=70003 + String format = "(?:" + color + ")? # Text color\n" + + "(?:\\[" + condition + "])? # Condition\n" + + // see https://msdn.microsoft.com/en-ca/goglobal/bb964664.aspx and https://bz.apache.org/ooo/show_bug.cgi?id=70003 // we ignore these for now though - "(?:\\[\\$-[0-9a-fA-F]+])? # Optional locale id, ignored currently\n" - + "((?:" + part + ")+) # Format spec\n"; + "(?:\\[\\$-[0-9a-fA-F]+])? # Optional locale id, ignored currently\n" + + "((?:" + part + ")+) # Format spec\n"; int flags = Pattern.COMMENTS | Pattern.CASE_INSENSITIVE; COLOR_PAT = Pattern.compile(color, flags); @@ -282,14 +264,14 @@ public class CellFormatPart { SPECIFICATION_GROUP = findGroup(FORMAT_PAT, "[Blue][>1]\\a ?", "\\a ?"); // Once patterns have been compiled, add indexed colors to - // NAMED_COLORS so they can be easily picked up by an unmodified + // NAMED_COLORS so they can be easily picked up by an unmodified // getColor() implementation for (int i = 0; i < INDEXED_COLORS.size(); ++i) { NAMED_COLORS.put("color " + (i + 1), INDEXED_COLORS.get(i)); } // NOTE: the INDEXED_COLORS list is retained for future use, even - // though it is not currently utilized outside the static - // initialization logic. + // though it is not currently utilized outside the static + // initialization logic. } interface PartHandler { @@ -300,8 +282,7 @@ String handlePart(Matcher m, String part, CellFormatType type, /** * Create an object to represent a format part. * - * @param desc - * The string to parse. + * @param desc The string to parse. */ public CellFormatPart(String desc) { this(LocaleUtil.getUserLocale(), desc); @@ -310,16 +291,14 @@ public CellFormatPart(String desc) { /** * Create an object to represent a format part. * - * @param locale - * The locale to use. - * @param desc - * The string to parse. + * @param locale The locale to use. + * @param desc The string to parse. */ public CellFormatPart(Locale locale, String desc) { Matcher m = FORMAT_PAT.matcher(desc); if (!m.matches()) { - throw new IllegalArgumentException( - "Unrecognized format: " + quote(desc)); + throw new IllegalArgumentException("Unrecognized format: " + quote( + desc)); } color = getColor(m); condition = getCondition(m); @@ -330,11 +309,10 @@ public CellFormatPart(Locale locale, String desc) { /** * Returns {@code true} if this format part applies to the given value. If * the value is a number and this is part has a condition, returns - * {@code true} only if the number passes the condition. Otherwise, this + * {@code true} only if the number passes the condition. Otherwise, this * always return {@code true}. * - * @param valueObject - * The value to evaluate. + * @param valueObject The value to evaluate. * * @return {@code true} if this format part applies to the given value. */ @@ -353,23 +331,20 @@ public boolean applies(Object valueObject) { * Returns the number of the first group that is the same as the marker * string. Starts from group 1. * - * @param pat - * The pattern to use. - * @param str - * The string to match against the pattern. - * @param marker - * The marker value to find the group of. + * @param pat The pattern to use. + * @param str The string to match against the pattern. + * @param marker The marker value to find the group of. * * @return The matching group number. * - * @throws IllegalArgumentException - * No group matches the marker. + * @throws IllegalArgumentException No group matches the marker. */ private static int findGroup(Pattern pat, String str, String marker) { Matcher m = pat.matcher(str); if (!m.find()) - throw new IllegalArgumentException("Pattern \"" + pat.pattern() - + "\" doesn't match \"" + str + "\""); + throw new IllegalArgumentException( + "Pattern \"" + pat.pattern() + "\" doesn't match \"" + str + + "\""); for (int i = 1; i <= m.groupCount(); i++) { String grp = m.group(i); if (grp != null && grp.equals(marker)) @@ -383,8 +358,7 @@ private static int findGroup(Pattern pat, String str, String marker) { * Returns the color specification from the matcher, or {@code null} if * there is none. * - * @param m - * The matcher for the format part. + * @param m The matcher for the format part. * * @return The color specification or {@code null}. */ @@ -406,8 +380,7 @@ private static Color getColor(Matcher m) { * Returns the condition specification from the matcher, or {@code null} if * there is none. * - * @param m - * The matcher for the format part. + * @param m The matcher for the format part. * * @return The condition specification or {@code null}. */ @@ -415,17 +388,15 @@ private CellFormatCondition getCondition(Matcher m) { String mdesc = m.group(CONDITION_OPERATOR_GROUP); if (mdesc == null || mdesc.length() == 0) return null; - return CellFormatCondition.getInstance( - m.group(CONDITION_OPERATOR_GROUP), - m.group(CONDITION_VALUE_GROUP)); + return CellFormatCondition.getInstance(m.group( + CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP)); } /** * Returns the CellFormatType object implied by the format specification for * the format part. * - * @param matcher - * The matcher for the format part. + * @param matcher The matcher for the format part. * * @return The CellFormatType. */ @@ -438,8 +409,7 @@ private CellFormatType getCellFormatType(Matcher matcher) { * Returns the formatter object implied by the format specification for the * format part. * - * @param matcher - * The matcher for the format part. + * @param matcher The matcher for the format part. * * @return The formatter. */ @@ -456,11 +426,9 @@ private CellFormatter getFormatter(Locale locale, Matcher matcher) { currencyRepl = "$"; } else if (!currencyPart.contains("-")) { // Accounting formats such as USD [$USD] - currencyRepl = currencyPart.substring(2, - currencyPart.indexOf("]")); + currencyRepl = currencyPart.substring(2, currencyPart.indexOf("]")); } else { - currencyRepl = currencyPart.substring(2, - currencyPart.lastIndexOf('-')); + currencyRepl = currencyPart.substring(2, currencyPart.lastIndexOf('-')); } fdesc = fdesc.replace(currencyPart, currencyRepl); } @@ -472,8 +440,7 @@ private CellFormatter getFormatter(Locale locale, Matcher matcher) { /** * Returns the type of format. * - * @param fdesc - * The format specification + * @param fdesc The format specification * * @return The type of format. */ @@ -524,9 +491,8 @@ private CellFormatType formatType(String fdesc) { return CellFormatType.NUMBER; } // Something else inside [] which isn't supported! - throw new IllegalArgumentException( - "Unsupported [] format block '" + repl + "' in '" - + fdesc + "' with c2: " + c2); + throw new IllegalArgumentException("Unsupported [] format block '" + + repl + "' in '" + fdesc + "' with c2: " + c2); case "#": case "?": return CellFormatType.NUMBER; @@ -544,13 +510,11 @@ private CellFormatType formatType(String fdesc) { /** * Returns a version of the original string that has any special characters - * quoted (or escaped) as appropriate for the cell format type. The format + * quoted (or escaped) as appropriate for the cell format type. The format * type object is queried to see what is special. * - * @param repl - * The original string. - * @param type - * The format type representation object. + * @param repl The original string. + * @param type The format type representation object. * * @return A version of the string with any special characters replaced. * @@ -558,8 +522,7 @@ private CellFormatType formatType(String fdesc) { */ static String quoteSpecial(String repl, CellFormatType type) { StringBuilder sb = new StringBuilder(); - PrimitiveIterator.OfInt codePoints = CodepointsUtil - .primitiveIterator(repl); + PrimitiveIterator.OfInt codePoints = CodepointsUtil.primitiveIterator(repl); int codepoint; while (codePoints.hasNext()) { @@ -581,11 +544,10 @@ static String quoteSpecial(String repl, CellFormatType type) { } /** - * Apply this format part to the given value. This returns a - * {@link CellFormatResult} object with the results. + * Apply this format part to the given value. This returns a {@link + * CellFormatResult} object with the results. * - * @param value - * The value to apply this format part to. + * @param value The value to apply this format part to. * * @return A {@link CellFormatResult} object containing the results of * applying the format to the value. @@ -608,10 +570,8 @@ public CellFormatResult apply(Object value) { * Apply this format part to the given value, applying the result to the * given label. * - * @param label - * The label - * @param value - * The value to apply this format part to. + * @param label The label + * @param value The value to apply this format part to. * * @return {@code true} if the */ @@ -646,19 +606,19 @@ boolean hasCondition() { public static StringBuffer parseFormat(String fdesc, CellFormatType type, PartHandler partHandler) { - // Quoting is very awkward. In the Java classes, quoting is done + // Quoting is very awkward. In the Java classes, quoting is done // between ' chars, with '' meaning a single ' char. The problem is that - // in Excel, it is legal to have two adjacent escaped strings. For - // example, consider the Excel format "\a\b#". The naive (and easy) - // translation into Java DecimalFormat is "'a''b'#". For the number 17, + // in Excel, it is legal to have two adjacent escaped strings. For + // example, consider the Excel format "\a\b#". The naive (and easy) + // translation into Java DecimalFormat is "'a''b'#". For the number 17, // in Excel you would get "ab17", but in Java it would be "a'b17" -- the - // '' is in the middle of the quoted string in Java. So the trick we + // '' is in the middle of the quoted string in Java. So the trick we // use is this: When we encounter a ' char in the Excel format, we - // output a \u0000 char into the string. Now we know that any '' in the - // output is the result of two adjacent escaped strings. So after the + // output a \u0000 char into the string. Now we know that any '' in the + // output is the result of two adjacent escaped strings. So after the // main loop, we have to do two passes: One to eliminate any '' // sequences, to make "'a''b'" become "'ab'", and another to replace any - // \u0000 with '' to mean a quote char. Oy. + // \u0000 with '' to mean a quote char. Oy. // // For formats that don't use "'" we don't do any of this Matcher m = SPECIFICATION_PAT.matcher(fdesc); @@ -670,8 +630,8 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, if (repl == null) { switch (part.charAt(0)) { case '\"': - repl = quoteSpecial( - part.substring(1, part.length() - 1), type); + repl = quoteSpecial(part.substring(1, + part.length() - 1), type); break; case '\\': repl = quoteSpecial(part.substring(1), type); @@ -679,8 +639,7 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, case '_': repl = " "; break; - case '*': // !! We don't do this for real, we just put in 3 - // of them + case '*': //!! We don't do this for real, we just put in 3 of them repl = expandChar(part); break; default: @@ -694,8 +653,7 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, m.appendTail(fmt); if (type.isSpecial('\'')) { - // Now the next pass for quoted characters: Remove '' chars, making - // "'a''b'" into "'ab'" + // Now the next pass for quoted characters: Remove '' chars, making "'a''b'" into "'ab'" int pos = 0; while ((pos = fmt.indexOf("''", pos)) >= 0) { fmt.delete(pos, pos + 2); @@ -721,21 +679,18 @@ public static StringBuffer parseFormat(String fdesc, CellFormatType type, /** * Expands a character. This is only partly done, because we don't have the - * correct info. In Excel, this would be expanded to fill the rest of the + * correct info. In Excel, this would be expanded to fill the rest of the * cell, but we don't know, in general, what the "rest of the cell" is. * - * @param part - * The character to be repeated is the second character in this - * string. + * @param part The character to be repeated is the second character in this + * string. * * @return The character repeated three times. */ static String expandChar(String part) { List codePoints = new ArrayList<>(); CodepointsUtil.iteratorFor(part).forEachRemaining(codePoints::add); - if (codePoints.size() < 2) - throw new IllegalArgumentException( - "Expected part string to have at least 2 chars"); + if (codePoints.size() < 2) throw new IllegalArgumentException("Expected part string to have at least 2 chars"); String ch = codePoints.get(1); return ch + ch + ch; } @@ -744,10 +699,8 @@ static String expandChar(String part) { * Returns the string from the group, or {@code ""} if the group is * {@code null}. * - * @param m - * The matcher. - * @param g - * The group number. + * @param m The matcher. + * @param g The group number. * * @return The group or {@code ""}. */ From 3a2f523866de64fc49c8f6b92b12995d54612af0 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Wed, 26 Feb 2025 13:00:22 +0200 Subject: [PATCH 6/8] make sure the custom cell formatting logic is used --- .../spreadsheet/CustomDataFormatter.java | 6 +- .../poi/ss/format/CustomCellFormat.java | 494 ++++++++++++++++++ ...matPart.java => CustomCellFormatPart.java} | 8 +- 3 files changed, 502 insertions(+), 6 deletions(-) create mode 100644 vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java rename vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/{CellFormatPart.java => CustomCellFormatPart.java} (99%) diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java index d5ed45480a0..94ca99c327c 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java @@ -13,8 +13,8 @@ import java.util.Locale; import java.util.regex.Pattern; -import org.apache.poi.ss.format.CellFormat; import org.apache.poi.ss.format.CellFormatResult; +import org.apache.poi.ss.format.CustomCellFormat; import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; @@ -138,7 +138,9 @@ private String formatNumericValueUsingFormatPart(Cell cell, private CellFormatResult formatTextUsingCellFormat(Cell cell, String format) { - return CellFormat.getInstance(locale, format).apply(cell); + // TODO: replace this with a reference to CellFormat when moving back to + // mainline Apache POI. + return CustomCellFormat.getInstance(locale, format).apply(cell); } /** diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java new file mode 100644 index 00000000000..5edaf31c5f6 --- /dev/null +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java @@ -0,0 +1,494 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.format; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.JLabel; + +import org.apache.logging.log4j.Logger; +import org.apache.poi.logging.PoiLogManager; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ConditionalFormatting; +import org.apache.poi.ss.usermodel.ConditionalFormattingRule; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.util.DateFormatConverter; +import org.apache.poi.util.LocaleUtil; + +/* + * Note: this file has been copied from Apache POI and modified to use the + * CustomCellFormatPart class. It is otherwise left intact. + */ + +/** + * Format a value according to the standard Excel behavior. This "standard" is + * not explicitly documented by Microsoft, so the behavior is determined by + * experimentation; see the tests. + *

+ * An Excel format has up to four parts, separated by semicolons. Each part + * specifies what to do with particular kinds of values, depending on the number + * of parts given: + *

+ *
One part (example: {@code [Green]#.##})
+ *
If the value is a number, display according to this one part (example: green text, + * with up to two decimal points). If the value is text, display it as is.
+ * + *
Two parts (example: {@code [Green]#.##;[Red]#.##})
+ *
If the value is a positive number or zero, display according to the first part (example: green + * text, with up to two decimal points); if it is a negative number, display + * according to the second part (example: red text, with up to two decimal + * points). If the value is text, display it as is.
+ * + *
Three parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##})
+ *
If the value is a positive + * number, display according to the first part (example: green text, with up to + * two decimal points); if it is zero, display according to the second part + * (example: black text, with up to two decimal points); if it is a negative + * number, display according to the third part (example: red text, with up to + * two decimal points). If the value is text, display it as is.
+ * + *
Four parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##;[@]})
+ *
If the value is a positive number, display according to the first part (example: green text, + * with up to two decimal points); if it is zero, display according to the + * second part (example: black text, with up to two decimal points); if it is a + * negative number, display according to the third part (example: red text, with + * up to two decimal points). If the value is text, display according to the + * fourth part (example: text in the cell's usual color, with the text value + * surround by brackets).
+ *
+ *

+ * A given format part may specify a given Locale, by including something + * like {@code [$$-409]} or {@code [$£-809]} or {@code [$-40C]}. These + * are (currently) largely ignored. You can use {@link DateFormatConverter} + * to look these up into Java Locales if desired. + *

+ * In addition to these, there is a general format that is used when no format + * is specified. + * + * TODO Merge this with {@link DataFormatter} so we only have one set of + * code for formatting numbers. + * TODO Re-use parts of this logic with {@link ConditionalFormatting} / + * {@link ConditionalFormattingRule} for reporting stylings which do/don't apply + * TODO Support the full set of modifiers, including alternate calendars and + * native character numbers, as documented at https://help.libreoffice.org/Common/Number_Format_Codes + */ +public class CustomCellFormat { + /** The logger to use in the formatting code. */ + private static final Logger LOG = PoiLogManager.getLogger(CustomCellFormat.class); + + private static final Pattern ONE_PART = Pattern.compile( + CustomCellFormatPart.FORMAT_PAT.pattern() + "(;|$)", + Pattern.COMMENTS | Pattern.CASE_INSENSITIVE); + + /* + * Cells that cannot be formatted, e.g. cells that have a date or time + * format and have an invalid date or time value, are displayed as 255 + * pound signs ("#"). + */ + private static final String INVALID_VALUE_FOR_FORMAT = + "###################################################" + + "###################################################" + + "###################################################" + + "###################################################" + + "###################################################"; + + private static final String QUOTE = "\""; + + private final Locale locale; + private final String format; + private final CustomCellFormatPart posNumFmt; + private final CustomCellFormatPart zeroNumFmt; + private final CustomCellFormatPart negNumFmt; + private final CustomCellFormatPart textFmt; + private final int formatPartCount; + + private static CustomCellFormat createGeneralFormat(final Locale locale) { + return new CustomCellFormat(locale, "General") { + @Override + public CellFormatResult apply(Object value) { + String text = (new CellGeneralFormatter(locale)).format(value); + return new CellFormatResult(true, text, null); + } + }; + } + + /** Maps a format string to its parsed version for efficiencies sake. */ + private static final Map> formatCache = + new WeakHashMap<>(); + + /** + * Returns a CellFormat that applies the given format. Two calls + * with the same format may or may not return the same object. + * + * @param format The format. + * + * @return A CellFormat that applies the given format. + */ + public static CustomCellFormat getInstance(String format) { + return getInstance(LocaleUtil.getUserLocale(), format); + } + + /** + * Returns a CellFormat that applies the given format. Two calls + * with the same format may or may not return the same object. + * + * @param locale The locale. + * @param format The format. + * + * @return A CellFormat that applies the given format. + */ + public static synchronized CustomCellFormat getInstance(Locale locale, String format) { + Map formatMap = formatCache.computeIfAbsent(locale, k -> new WeakHashMap<>()); + CustomCellFormat fmt = formatMap.get(format); + if (fmt == null) { + if (format.equals("General") || format.equals("@")) + fmt = createGeneralFormat(locale); + else + fmt = new CustomCellFormat(locale, format); + formatMap.put(format, fmt); + } + return fmt; + } + + /** + * Creates a new object. + * + * @param format The format. + */ + private CustomCellFormat(Locale locale, String format) { + this.locale = locale; + this.format = format; + CustomCellFormatPart defaultTextFormat = new CustomCellFormatPart(locale, "@"); + Matcher m = ONE_PART.matcher(format); + List parts = new ArrayList<>(); + + try { + while (m.find()) { + try { + String valueDesc = m.group(); + + // Strip out the semicolon if it's there + if (valueDesc.endsWith(";")) + valueDesc = valueDesc.substring(0, valueDesc.length() - 1); + + parts.add(new CustomCellFormatPart(locale, valueDesc)); + } catch (RuntimeException e) { + LOG.warn("Invalid format: {}", CellFormatter.quote(m.group()), e); + parts.add(null); + } + } + } catch (StackOverflowError e) { + // very complex formats can cause the regex-parsing to exceed the available stack + // we want to handle this more gracefully by catching it and reporting a bit more + // details in the error message + throw new IllegalStateException("The provided format is too complex: " + format + + ", you can try to increase Java Stack size via commandline argument '-Xss' " + + "to allow handling this format"); + } + + formatPartCount = parts.size(); + + switch (formatPartCount) { + case 1: + posNumFmt = parts.get(0); + negNumFmt = null; + zeroNumFmt = null; + textFmt = defaultTextFormat; + break; + case 2: + posNumFmt = parts.get(0); + negNumFmt = parts.get(1); + zeroNumFmt = null; + textFmt = defaultTextFormat; + break; + case 3: + posNumFmt = parts.get(0); + negNumFmt = parts.get(1); + zeroNumFmt = parts.get(2); + textFmt = defaultTextFormat; + break; + case 4: + default: + posNumFmt = parts.get(0); + negNumFmt = parts.get(1); + zeroNumFmt = parts.get(2); + textFmt = parts.get(3); + break; + } + } + + /** + * Returns the result of applying the format to the given value. If the + * value is a number (a type of {@link Number} object), the correct number + * format type is chosen; otherwise it is considered a text object. + * + * @param value The value + * + * @return The result, in a {@link CellFormatResult}. + */ + public CellFormatResult apply(Object value) { + if (value instanceof Number) { + Number num = (Number) value; + double val = num.doubleValue(); + if (val < 0 && + ((formatPartCount == 2 + && !posNumFmt.hasCondition() && !negNumFmt.hasCondition()) + || (formatPartCount == 3 && !negNumFmt.hasCondition()) + || (formatPartCount == 4 && !negNumFmt.hasCondition()))) { + // The negative number format has the negative formatting required, + // e.g. minus sign or brackets, so pass a positive value so that + // the default leading minus sign is not also output + return negNumFmt.apply(-val); + } else { + return getApplicableFormatPart(val).apply(val); + } + } else if (value instanceof java.util.Date) { + // Don't know (and can't get) the workbook date windowing (1900 or 1904) + // so assume 1900 date windowing + double numericValue = DateUtil.getExcelDate((Date) value); + if (DateUtil.isValidExcelDate(numericValue)) { + return getApplicableFormatPart(numericValue).apply(value); + } else { + throw new IllegalArgumentException("value " + numericValue + " of date " + value + " is not a valid Excel date"); + } + } else { + return textFmt.apply(value); + } + } + + /** + * Returns the result of applying the format to the given date. + * + * @param date The date. + * @param numericValue The numeric value for the date. + * + * @return The result, in a {@link CellFormatResult}. + */ + private CellFormatResult apply(Date date, double numericValue) { + return getApplicableFormatPart(numericValue).apply(date); + } + + /** + * Fetches the appropriate value from the cell, and returns the result of + * applying it to the appropriate format. For formula cells, the computed + * value is what is used. + * + * @param c The cell. + * + * @return The result, in a {@link CellFormatResult}. + */ + public CellFormatResult apply(Cell c) { + switch (ultimateType(c)) { + case BLANK: + return apply(""); + case BOOLEAN: + return apply(c.getBooleanCellValue()); + case NUMERIC: + double value = c.getNumericCellValue(); + if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) { + if (DateUtil.isValidExcelDate(value)) { + return apply(c.getDateCellValue(), value); + } else { + return apply(INVALID_VALUE_FOR_FORMAT); + } + } else { + return apply(value); + } + case STRING: + return apply(c.getStringCellValue()); + default: + return apply("?"); + } + } + + /** + * Uses the result of applying this format to the value, setting the text + * and color of a label before returning the result. + * + * @param label The label to apply to. + * @param value The value to process. + * + * @return The result, in a {@link CellFormatResult}. + */ + public CellFormatResult apply(JLabel label, Object value) { + CellFormatResult result = apply(value); + label.setText(result.text); + if (result.textColor != null) { + label.setForeground(result.textColor); + } + return result; + } + + /** + * Uses the result of applying this format to the given date, setting the text + * and color of a label before returning the result. + * + * @param label The label to apply to. + * @param date The date. + * @param numericValue The numeric value for the date. + * + * @return The result, in a {@link CellFormatResult}. + */ + private CellFormatResult apply(JLabel label, Date date, double numericValue) { + CellFormatResult result = apply(date, numericValue); + label.setText(result.text); + if (result.textColor != null) { + label.setForeground(result.textColor); + } + return result; + } + + /** + * Fetches the appropriate value from the cell, and uses the result, setting + * the text and color of a label before returning the result. + * + * @param label The label to apply to. + * @param c The cell. + * + * @return The result, in a {@link CellFormatResult}. + */ + public CellFormatResult apply(JLabel label, Cell c) { + switch (ultimateType(c)) { + case BLANK: + return apply(label, ""); + case BOOLEAN: + return apply(label, c.getBooleanCellValue()); + case NUMERIC: + double value = c.getNumericCellValue(); + if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) { + if (DateUtil.isValidExcelDate(value)) { + return apply(label, c.getDateCellValue(), value); + } else { + return apply(label, INVALID_VALUE_FOR_FORMAT); + } + } else { + return apply(label, value); + } + case STRING: + return apply(label, c.getStringCellValue()); + default: + return apply(label, "?"); + } + } + + /** + * Returns the {@link CustomCellFormatPart} that applies to the value. Result + * depends on how many parts the cell format has, the cell value and any + * conditions. The value must be a {@link Number}. + * + * @param value The value. + * @return The {@link CustomCellFormatPart} that applies to the value. + */ + private CustomCellFormatPart getApplicableFormatPart(Object value) { + + if (value instanceof Number) { + + double val = ((Number) value).doubleValue(); + + if (formatPartCount == 1) { + if (!posNumFmt.hasCondition() + || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { + return posNumFmt; + } else { + return new CustomCellFormatPart(locale, "General"); + } + } else if (formatPartCount == 2) { + if ((!posNumFmt.hasCondition() && val >= 0) + || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { + return posNumFmt; + } else if (!negNumFmt.hasCondition() + || (negNumFmt.hasCondition() && negNumFmt.applies(val))) { + return negNumFmt; + } else { + // Return ###...### (255 #s) to match Excel 2007 behaviour + return new CustomCellFormatPart(QUOTE + INVALID_VALUE_FOR_FORMAT + QUOTE); + } + } else { + if ((!posNumFmt.hasCondition() && val > 0) + || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { + return posNumFmt; + } else if ((!negNumFmt.hasCondition() && val < 0) + || (negNumFmt.hasCondition() && negNumFmt.applies(val))) { + return negNumFmt; + // Only the first two format parts can have conditions + } else { + return zeroNumFmt; + } + } + } else { + throw new IllegalArgumentException("value must be a Number"); + } + + } + + /** + * Returns the ultimate cell type, following the results of formulas. If + * the cell is a {@link CellType#FORMULA}, this returns the result of + * {@link Cell#getCachedFormulaResultType()}. Otherwise this returns the + * result of {@link Cell#getCellType()}. + * + * @param cell The cell. + * + * @return The ultimate type of this cell. + */ + public static CellType ultimateType(Cell cell) { + CellType type = cell.getCellType(); + if (type == CellType.FORMULA) + return cell.getCachedFormulaResultType(); + else + return type; + } + + /** + * Returns {@code true} if the other object is a CellFormat object + * with the same format. + * + * @param obj The other object. + * + * @return {@code true} if the two objects are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof CustomCellFormat) { + CustomCellFormat that = (CustomCellFormat) obj; + return format.equals(that.format); + } + return false; + } + + /** + * Returns a hash code for the format. + * + * @return A hash code for the format. + */ + @Override + public int hashCode() { + return format.hashCode(); + } +} diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java similarity index 99% rename from vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java rename to vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java index 730416e859c..770669fe8fc 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java @@ -63,8 +63,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more * code has use for them. */ @SuppressWarnings("RegExpRepeatedSpace") -public class CellFormatPart { - private static final Logger LOG = LogManager.getLogger(CellFormatPart.class); +public class CustomCellFormatPart { + private static final Logger LOG = LogManager.getLogger(CustomCellFormatPart.class); static final Map NAMED_COLORS; static final List INDEXED_COLORS; @@ -284,7 +284,7 @@ String handlePart(Matcher m, String part, CellFormatType type, * * @param desc The string to parse. */ - public CellFormatPart(String desc) { + public CustomCellFormatPart(String desc) { this(LocaleUtil.getUserLocale(), desc); } @@ -294,7 +294,7 @@ public CellFormatPart(String desc) { * @param locale The locale to use. * @param desc The string to parse. */ - public CellFormatPart(Locale locale, String desc) { + public CustomCellFormatPart(Locale locale, String desc) { Matcher m = FORMAT_PAT.matcher(desc); if (!m.matches()) { throw new IllegalArgumentException("Unrecognized format: " + quote( From f88c625a77f38407eb9cb643034bdeb87c310ee7 Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Wed, 26 Feb 2025 16:24:23 +0200 Subject: [PATCH 7/8] Refactor project to try to load our hack as early as possible --- .../spreadsheet/CustomDataFormatter.java | 4 +- .../component/spreadsheet/Spreadsheet.java | 6 + ...ellFormatPart.java => CellFormatPart.java} | 8 +- .../poi/ss/format/CustomCellFormat.java | 494 ------------------ 4 files changed, 12 insertions(+), 500 deletions(-) rename vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/{CustomCellFormatPart.java => CellFormatPart.java} (99%) delete mode 100644 vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java index 94ca99c327c..aa9a724dc3b 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; import org.apache.poi.ss.format.CellFormatResult; -import org.apache.poi.ss.format.CustomCellFormat; +import org.apache.poi.ss.format.CellFormat; import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; @@ -140,7 +140,7 @@ private CellFormatResult formatTextUsingCellFormat(Cell cell, String format) { // TODO: replace this with a reference to CellFormat when moving back to // mainline Apache POI. - return CustomCellFormat.getInstance(locale, format).apply(cell); + return CellFormat.getInstance(locale, format).apply(cell); } /** diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/Spreadsheet.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/Spreadsheet.java index 2060ee51477..741b86917ee 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/Spreadsheet.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/Spreadsheet.java @@ -36,6 +36,7 @@ import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.format.CellFormatPart; import org.apache.poi.ss.formula.BaseFormulaEvaluator; import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import org.apache.poi.ss.usermodel.Cell; @@ -141,6 +142,11 @@ public class Spreadsheet extends Component "vaadin-spreadsheet-flow", version); } } + + // Force load our variant CellFormatPart.java + // TODO: this is a hack; remove this once color support is fixed in POI + CellFormatPart cf = new CellFormatPart("general"); + cf.applies(1000d); } @Override diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java similarity index 99% rename from vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java rename to vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java index 770669fe8fc..730416e859c 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormatPart.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -63,8 +63,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more * code has use for them. */ @SuppressWarnings("RegExpRepeatedSpace") -public class CustomCellFormatPart { - private static final Logger LOG = LogManager.getLogger(CustomCellFormatPart.class); +public class CellFormatPart { + private static final Logger LOG = LogManager.getLogger(CellFormatPart.class); static final Map NAMED_COLORS; static final List INDEXED_COLORS; @@ -284,7 +284,7 @@ String handlePart(Matcher m, String part, CellFormatType type, * * @param desc The string to parse. */ - public CustomCellFormatPart(String desc) { + public CellFormatPart(String desc) { this(LocaleUtil.getUserLocale(), desc); } @@ -294,7 +294,7 @@ public CustomCellFormatPart(String desc) { * @param locale The locale to use. * @param desc The string to parse. */ - public CustomCellFormatPart(Locale locale, String desc) { + public CellFormatPart(Locale locale, String desc) { Matcher m = FORMAT_PAT.matcher(desc); if (!m.matches()) { throw new IllegalArgumentException("Unrecognized format: " + quote( diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java deleted file mode 100644 index 5edaf31c5f6..00000000000 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/org/apache/poi/ss/format/CustomCellFormat.java +++ /dev/null @@ -1,494 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.format; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.swing.JLabel; - -import org.apache.logging.log4j.Logger; -import org.apache.poi.logging.PoiLogManager; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellType; -import org.apache.poi.ss.usermodel.ConditionalFormatting; -import org.apache.poi.ss.usermodel.ConditionalFormattingRule; -import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.ss.usermodel.DateUtil; -import org.apache.poi.ss.util.DateFormatConverter; -import org.apache.poi.util.LocaleUtil; - -/* - * Note: this file has been copied from Apache POI and modified to use the - * CustomCellFormatPart class. It is otherwise left intact. - */ - -/** - * Format a value according to the standard Excel behavior. This "standard" is - * not explicitly documented by Microsoft, so the behavior is determined by - * experimentation; see the tests. - *

- * An Excel format has up to four parts, separated by semicolons. Each part - * specifies what to do with particular kinds of values, depending on the number - * of parts given: - *

- *
One part (example: {@code [Green]#.##})
- *
If the value is a number, display according to this one part (example: green text, - * with up to two decimal points). If the value is text, display it as is.
- * - *
Two parts (example: {@code [Green]#.##;[Red]#.##})
- *
If the value is a positive number or zero, display according to the first part (example: green - * text, with up to two decimal points); if it is a negative number, display - * according to the second part (example: red text, with up to two decimal - * points). If the value is text, display it as is.
- * - *
Three parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##})
- *
If the value is a positive - * number, display according to the first part (example: green text, with up to - * two decimal points); if it is zero, display according to the second part - * (example: black text, with up to two decimal points); if it is a negative - * number, display according to the third part (example: red text, with up to - * two decimal points). If the value is text, display it as is.
- * - *
Four parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##;[@]})
- *
If the value is a positive number, display according to the first part (example: green text, - * with up to two decimal points); if it is zero, display according to the - * second part (example: black text, with up to two decimal points); if it is a - * negative number, display according to the third part (example: red text, with - * up to two decimal points). If the value is text, display according to the - * fourth part (example: text in the cell's usual color, with the text value - * surround by brackets).
- *
- *

- * A given format part may specify a given Locale, by including something - * like {@code [$$-409]} or {@code [$£-809]} or {@code [$-40C]}. These - * are (currently) largely ignored. You can use {@link DateFormatConverter} - * to look these up into Java Locales if desired. - *

- * In addition to these, there is a general format that is used when no format - * is specified. - * - * TODO Merge this with {@link DataFormatter} so we only have one set of - * code for formatting numbers. - * TODO Re-use parts of this logic with {@link ConditionalFormatting} / - * {@link ConditionalFormattingRule} for reporting stylings which do/don't apply - * TODO Support the full set of modifiers, including alternate calendars and - * native character numbers, as documented at https://help.libreoffice.org/Common/Number_Format_Codes - */ -public class CustomCellFormat { - /** The logger to use in the formatting code. */ - private static final Logger LOG = PoiLogManager.getLogger(CustomCellFormat.class); - - private static final Pattern ONE_PART = Pattern.compile( - CustomCellFormatPart.FORMAT_PAT.pattern() + "(;|$)", - Pattern.COMMENTS | Pattern.CASE_INSENSITIVE); - - /* - * Cells that cannot be formatted, e.g. cells that have a date or time - * format and have an invalid date or time value, are displayed as 255 - * pound signs ("#"). - */ - private static final String INVALID_VALUE_FOR_FORMAT = - "###################################################" + - "###################################################" + - "###################################################" + - "###################################################" + - "###################################################"; - - private static final String QUOTE = "\""; - - private final Locale locale; - private final String format; - private final CustomCellFormatPart posNumFmt; - private final CustomCellFormatPart zeroNumFmt; - private final CustomCellFormatPart negNumFmt; - private final CustomCellFormatPart textFmt; - private final int formatPartCount; - - private static CustomCellFormat createGeneralFormat(final Locale locale) { - return new CustomCellFormat(locale, "General") { - @Override - public CellFormatResult apply(Object value) { - String text = (new CellGeneralFormatter(locale)).format(value); - return new CellFormatResult(true, text, null); - } - }; - } - - /** Maps a format string to its parsed version for efficiencies sake. */ - private static final Map> formatCache = - new WeakHashMap<>(); - - /** - * Returns a CellFormat that applies the given format. Two calls - * with the same format may or may not return the same object. - * - * @param format The format. - * - * @return A CellFormat that applies the given format. - */ - public static CustomCellFormat getInstance(String format) { - return getInstance(LocaleUtil.getUserLocale(), format); - } - - /** - * Returns a CellFormat that applies the given format. Two calls - * with the same format may or may not return the same object. - * - * @param locale The locale. - * @param format The format. - * - * @return A CellFormat that applies the given format. - */ - public static synchronized CustomCellFormat getInstance(Locale locale, String format) { - Map formatMap = formatCache.computeIfAbsent(locale, k -> new WeakHashMap<>()); - CustomCellFormat fmt = formatMap.get(format); - if (fmt == null) { - if (format.equals("General") || format.equals("@")) - fmt = createGeneralFormat(locale); - else - fmt = new CustomCellFormat(locale, format); - formatMap.put(format, fmt); - } - return fmt; - } - - /** - * Creates a new object. - * - * @param format The format. - */ - private CustomCellFormat(Locale locale, String format) { - this.locale = locale; - this.format = format; - CustomCellFormatPart defaultTextFormat = new CustomCellFormatPart(locale, "@"); - Matcher m = ONE_PART.matcher(format); - List parts = new ArrayList<>(); - - try { - while (m.find()) { - try { - String valueDesc = m.group(); - - // Strip out the semicolon if it's there - if (valueDesc.endsWith(";")) - valueDesc = valueDesc.substring(0, valueDesc.length() - 1); - - parts.add(new CustomCellFormatPart(locale, valueDesc)); - } catch (RuntimeException e) { - LOG.warn("Invalid format: {}", CellFormatter.quote(m.group()), e); - parts.add(null); - } - } - } catch (StackOverflowError e) { - // very complex formats can cause the regex-parsing to exceed the available stack - // we want to handle this more gracefully by catching it and reporting a bit more - // details in the error message - throw new IllegalStateException("The provided format is too complex: " + format + - ", you can try to increase Java Stack size via commandline argument '-Xss' " + - "to allow handling this format"); - } - - formatPartCount = parts.size(); - - switch (formatPartCount) { - case 1: - posNumFmt = parts.get(0); - negNumFmt = null; - zeroNumFmt = null; - textFmt = defaultTextFormat; - break; - case 2: - posNumFmt = parts.get(0); - negNumFmt = parts.get(1); - zeroNumFmt = null; - textFmt = defaultTextFormat; - break; - case 3: - posNumFmt = parts.get(0); - negNumFmt = parts.get(1); - zeroNumFmt = parts.get(2); - textFmt = defaultTextFormat; - break; - case 4: - default: - posNumFmt = parts.get(0); - negNumFmt = parts.get(1); - zeroNumFmt = parts.get(2); - textFmt = parts.get(3); - break; - } - } - - /** - * Returns the result of applying the format to the given value. If the - * value is a number (a type of {@link Number} object), the correct number - * format type is chosen; otherwise it is considered a text object. - * - * @param value The value - * - * @return The result, in a {@link CellFormatResult}. - */ - public CellFormatResult apply(Object value) { - if (value instanceof Number) { - Number num = (Number) value; - double val = num.doubleValue(); - if (val < 0 && - ((formatPartCount == 2 - && !posNumFmt.hasCondition() && !negNumFmt.hasCondition()) - || (formatPartCount == 3 && !negNumFmt.hasCondition()) - || (formatPartCount == 4 && !negNumFmt.hasCondition()))) { - // The negative number format has the negative formatting required, - // e.g. minus sign or brackets, so pass a positive value so that - // the default leading minus sign is not also output - return negNumFmt.apply(-val); - } else { - return getApplicableFormatPart(val).apply(val); - } - } else if (value instanceof java.util.Date) { - // Don't know (and can't get) the workbook date windowing (1900 or 1904) - // so assume 1900 date windowing - double numericValue = DateUtil.getExcelDate((Date) value); - if (DateUtil.isValidExcelDate(numericValue)) { - return getApplicableFormatPart(numericValue).apply(value); - } else { - throw new IllegalArgumentException("value " + numericValue + " of date " + value + " is not a valid Excel date"); - } - } else { - return textFmt.apply(value); - } - } - - /** - * Returns the result of applying the format to the given date. - * - * @param date The date. - * @param numericValue The numeric value for the date. - * - * @return The result, in a {@link CellFormatResult}. - */ - private CellFormatResult apply(Date date, double numericValue) { - return getApplicableFormatPart(numericValue).apply(date); - } - - /** - * Fetches the appropriate value from the cell, and returns the result of - * applying it to the appropriate format. For formula cells, the computed - * value is what is used. - * - * @param c The cell. - * - * @return The result, in a {@link CellFormatResult}. - */ - public CellFormatResult apply(Cell c) { - switch (ultimateType(c)) { - case BLANK: - return apply(""); - case BOOLEAN: - return apply(c.getBooleanCellValue()); - case NUMERIC: - double value = c.getNumericCellValue(); - if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) { - if (DateUtil.isValidExcelDate(value)) { - return apply(c.getDateCellValue(), value); - } else { - return apply(INVALID_VALUE_FOR_FORMAT); - } - } else { - return apply(value); - } - case STRING: - return apply(c.getStringCellValue()); - default: - return apply("?"); - } - } - - /** - * Uses the result of applying this format to the value, setting the text - * and color of a label before returning the result. - * - * @param label The label to apply to. - * @param value The value to process. - * - * @return The result, in a {@link CellFormatResult}. - */ - public CellFormatResult apply(JLabel label, Object value) { - CellFormatResult result = apply(value); - label.setText(result.text); - if (result.textColor != null) { - label.setForeground(result.textColor); - } - return result; - } - - /** - * Uses the result of applying this format to the given date, setting the text - * and color of a label before returning the result. - * - * @param label The label to apply to. - * @param date The date. - * @param numericValue The numeric value for the date. - * - * @return The result, in a {@link CellFormatResult}. - */ - private CellFormatResult apply(JLabel label, Date date, double numericValue) { - CellFormatResult result = apply(date, numericValue); - label.setText(result.text); - if (result.textColor != null) { - label.setForeground(result.textColor); - } - return result; - } - - /** - * Fetches the appropriate value from the cell, and uses the result, setting - * the text and color of a label before returning the result. - * - * @param label The label to apply to. - * @param c The cell. - * - * @return The result, in a {@link CellFormatResult}. - */ - public CellFormatResult apply(JLabel label, Cell c) { - switch (ultimateType(c)) { - case BLANK: - return apply(label, ""); - case BOOLEAN: - return apply(label, c.getBooleanCellValue()); - case NUMERIC: - double value = c.getNumericCellValue(); - if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) { - if (DateUtil.isValidExcelDate(value)) { - return apply(label, c.getDateCellValue(), value); - } else { - return apply(label, INVALID_VALUE_FOR_FORMAT); - } - } else { - return apply(label, value); - } - case STRING: - return apply(label, c.getStringCellValue()); - default: - return apply(label, "?"); - } - } - - /** - * Returns the {@link CustomCellFormatPart} that applies to the value. Result - * depends on how many parts the cell format has, the cell value and any - * conditions. The value must be a {@link Number}. - * - * @param value The value. - * @return The {@link CustomCellFormatPart} that applies to the value. - */ - private CustomCellFormatPart getApplicableFormatPart(Object value) { - - if (value instanceof Number) { - - double val = ((Number) value).doubleValue(); - - if (formatPartCount == 1) { - if (!posNumFmt.hasCondition() - || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { - return posNumFmt; - } else { - return new CustomCellFormatPart(locale, "General"); - } - } else if (formatPartCount == 2) { - if ((!posNumFmt.hasCondition() && val >= 0) - || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { - return posNumFmt; - } else if (!negNumFmt.hasCondition() - || (negNumFmt.hasCondition() && negNumFmt.applies(val))) { - return negNumFmt; - } else { - // Return ###...### (255 #s) to match Excel 2007 behaviour - return new CustomCellFormatPart(QUOTE + INVALID_VALUE_FOR_FORMAT + QUOTE); - } - } else { - if ((!posNumFmt.hasCondition() && val > 0) - || (posNumFmt.hasCondition() && posNumFmt.applies(val))) { - return posNumFmt; - } else if ((!negNumFmt.hasCondition() && val < 0) - || (negNumFmt.hasCondition() && negNumFmt.applies(val))) { - return negNumFmt; - // Only the first two format parts can have conditions - } else { - return zeroNumFmt; - } - } - } else { - throw new IllegalArgumentException("value must be a Number"); - } - - } - - /** - * Returns the ultimate cell type, following the results of formulas. If - * the cell is a {@link CellType#FORMULA}, this returns the result of - * {@link Cell#getCachedFormulaResultType()}. Otherwise this returns the - * result of {@link Cell#getCellType()}. - * - * @param cell The cell. - * - * @return The ultimate type of this cell. - */ - public static CellType ultimateType(Cell cell) { - CellType type = cell.getCellType(); - if (type == CellType.FORMULA) - return cell.getCachedFormulaResultType(); - else - return type; - } - - /** - * Returns {@code true} if the other object is a CellFormat object - * with the same format. - * - * @param obj The other object. - * - * @return {@code true} if the two objects are equal. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj instanceof CustomCellFormat) { - CustomCellFormat that = (CustomCellFormat) obj; - return format.equals(that.format); - } - return false; - } - - /** - * Returns a hash code for the format. - * - * @return A hash code for the format. - */ - @Override - public int hashCode() { - return format.hashCode(); - } -} From 07300b3034005cc885fb4e8f2f468ce8c25c6cce Mon Sep 17 00:00:00 2001 From: thevaadinman Date: Wed, 26 Feb 2025 17:06:54 +0200 Subject: [PATCH 8/8] Update CustomDataFormatter.java --- .../vaadin/flow/component/spreadsheet/CustomDataFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java index aa9a724dc3b..3b073da1134 100644 --- a/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java +++ b/vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/java/com/vaadin/flow/component/spreadsheet/CustomDataFormatter.java @@ -13,8 +13,8 @@ import java.util.Locale; import java.util.regex.Pattern; -import org.apache.poi.ss.format.CellFormatResult; import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType;