diff --git a/src/java/org/apache/poi/ss/format/CellFormat.java b/src/java/org/apache/poi/ss/format/CellFormat.java index e5333d601..40cca8c3c 100644 --- a/src/java/org/apache/poi/ss/format/CellFormat.java +++ b/src/java/org/apache/poi/ss/format/CellFormat.java @@ -66,7 +66,7 @@ import org.apache.poi.ss.util.DateFormatConverter; * surround by brackets). *

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

@@ -78,6 +78,8 @@ import org.apache.poi.ss.util.DateFormatConverter; * 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 CellFormat { private final String format; diff --git a/src/java/org/apache/poi/ss/format/CellFormatPart.java b/src/java/org/apache/poi/ss/format/CellFormatPart.java index 40d8c0849..6bd612838 100644 --- a/src/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/src/java/org/apache/poi/ss/format/CellFormatPart.java @@ -19,6 +19,7 @@ package org.apache.poi.ss.format; import org.apache.poi.hssf.util.HSSFColor; import javax.swing.*; + import java.awt.*; import java.util.Locale; import java.util.Map; @@ -80,6 +81,8 @@ public class CellFormatPart { 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; @@ -105,6 +108,9 @@ public class CellFormatPart { // A condition specification String condition = "([<>=]=?|!=|<>) # The operator\n" + " \\s*([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}\\])"; String color = "\\[(black|blue|cyan|green|magenta|red|white|yellow|color [0-9]+)\\]"; @@ -115,6 +121,7 @@ public class CellFormatPart { // A part of a specification 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" + @@ -131,14 +138,15 @@ public class CellFormatPart { "|\\[s{1,2}\\] # Elapsed time: second spec\n" + "|[^;] # A character\n" + ""; - String format = "(?:" + color + ")? # Text color\n" + - "(?:\\[" + condition + "\\])? # Condition\n" + + String format = "(?:" + color + ")? # Text color\n" + + "(?:\\[" + condition + "\\])? # Condition\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 @@ -196,7 +204,7 @@ public class CellFormatPart { /** * Returns the number of the first group that is the same as the marker - * string. The search starts with group 1. + * string. Starts from group 1. * * @param pat The pattern to use. * @param str The string to match against the pattern. @@ -278,6 +286,22 @@ public class CellFormatPart { */ private CellFormatter getFormatter(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 { + currencyRepl = currencyPart.substring(2, currencyPart.lastIndexOf('-')); + } + fdesc = fdesc.replace(currencyPart, currencyRepl); + } + + // Build a formatter for this simplified string return type.formatter(fdesc); } @@ -298,8 +322,14 @@ public class CellFormatPart { boolean seenZero = false; while (m.find()) { String repl = m.group(0); + if (repl.length() > 0) { - switch (repl.charAt(0)) { + char c1 = repl.charAt(0); + char c2 = 0; + if (repl.length() > 1) + c2 = Character.toLowerCase(repl.charAt(1)); + + switch (c1) { case '@': return CellFormatType.TEXT; case 'd': @@ -321,7 +351,16 @@ public class CellFormatPart { seenZero = true; break; case '[': - return CellFormatType.ELAPSED; + if (c2 == 'h' || c2 == 'm' || c2 == 's') { + return CellFormatType.ELAPSED; + } + if (c2 == '$') { + // Localised currency + return CellFormatType.NUMBER; + } + // Something else inside [] which isn't supported! + throw new IllegalArgumentException("Unsupported [] format block '" + + repl + "' in '" + fdesc + "'"); case '#': case '?': return CellFormatType.NUMBER; diff --git a/src/testcases/org/apache/poi/ss/format/TestCellFormat.java b/src/testcases/org/apache/poi/ss/format/TestCellFormat.java index 13a1f4b2e..97bb6e1e6 100644 --- a/src/testcases/org/apache/poi/ss/format/TestCellFormat.java +++ b/src/testcases/org/apache/poi/ss/format/TestCellFormat.java @@ -36,7 +36,6 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.LocaleUtil; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; public class TestCellFormat { @@ -918,7 +917,7 @@ public class TestCellFormat { Row row = sheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue(123456.6); - System.out.println(cf1.apply(cell).text); + //System.out.println(cf1.apply(cell).text); assertEquals("123456 3/5", cf1.apply(cell).text); } finally { wb.close(); @@ -926,7 +925,6 @@ public class TestCellFormat { } @Test - @Ignore("TODO") // TODO public void testAccountingFormats() throws IOException { char pound = '\u00A3'; char euro = '\u20AC'; @@ -934,11 +932,11 @@ public class TestCellFormat { // Accounting -> 0 decimal places, default currency symbol String formatDft = "_-\"$\"* #,##0_-;\\-\"$\"* #,##0_-;_-\"$\"* \"-\"_-;_-@_-"; // Accounting -> 0 decimal places, US currency symbol - String formatUS = "_-[$$-409]* #,##0_ ;_-[$$-409]* -#,##0 ;_-[$$-409]* \"-\"_ ;_-@_ "; + String formatUS = "_-[$$-409]* #,##0_ ;_-[$$-409]* -#,##0 ;_-[$$-409]* \"-\"_-;_-@_-"; // Accounting -> 0 decimal places, UK currency symbol String formatUK = "_-[$"+pound+"-809]* #,##0_-;\\-[$"+pound+"-809]* #,##0_-;_-[$"+pound+"-809]* \"-\"??_-;_-@_-"; - // Accounting -> 0 decimal places, French currency symbol - String formatFR = "_-[$"+euro+"-40C]* #,##0_-;\\-[$"+euro+"-40C]* #,##0_-;_-[$"+euro+"-40C]* \"-\"??_-;_-@_-"; + // French style accounting, euro sign comes after not before + String formatFR = "_-#,##0* [$"+euro+"-40C]_-;\\-#,##0* [$"+euro+"-40C]_-;_-\"-\"??* [$"+euro+"-40C] _-;_-@_-"; // Has +ve, -ve and zero rules CellFormat cfDft = CellFormat.getInstance(formatDft); @@ -947,17 +945,34 @@ public class TestCellFormat { CellFormat cfFR = CellFormat.getInstance(formatFR); // For +ve numbers, should be Space + currency symbol + spaces + whole number with commas + space - assertEquals(" $ 12 ",cfDft.apply(Double.valueOf(12.33)).text); - assertEquals(" $ 12 ", cfUS.apply(Double.valueOf(12.33)).text); + // (Except French, which is mostly reversed...) + assertEquals(" $ 12 ", cfDft.apply(Double.valueOf(12.33)).text); + assertEquals(" $ 12 ", cfUS.apply(Double.valueOf(12.33)).text); assertEquals(" "+pound+" 12 ", cfUK.apply(Double.valueOf(12.33)).text); - assertEquals(" "+pound+" 12 ", cfFR.apply(Double.valueOf(12.33)).text); - assertEquals(" "+pound+" 16,789 ", cfUK.apply(Double.valueOf(16789.2)).text); - // TODO More + assertEquals(" 12 "+euro+" ", cfFR.apply(Double.valueOf(12.33)).text); - // For -ve numbers, should be Minus + currency symbol + spaces + whole number with commas - // TODO + assertEquals(" $ 16,789 ", cfDft.apply(Double.valueOf(16789.2)).text); + assertEquals(" $ 16,789 ", cfUS.apply(Double.valueOf(16789.2)).text); + assertEquals(" "+pound+" 16,789 ", cfUK.apply(Double.valueOf(16789.2)).text); + assertEquals(" 16,789 "+euro+" ", cfFR.apply(Double.valueOf(16789.2)).text); + + // For -ve numbers, gets a bit more complicated... + assertEquals("-$ 12 ", cfDft.apply(Double.valueOf(-12.33)).text); + assertEquals(" $ -12 ", cfUS.apply(Double.valueOf(-12.33)).text); + assertEquals("-"+pound+" 12 ", cfUK.apply(Double.valueOf(-12.33)).text); + assertEquals("-12 "+euro+" ", cfFR.apply(Double.valueOf(-12.33)).text); + + assertEquals("-$ 16,789 ", cfDft.apply(Double.valueOf(-16789.2)).text); + assertEquals(" $ -16,789 ", cfUS.apply(Double.valueOf(-16789.2)).text); + assertEquals("-"+pound+" 16,789 ", cfUK.apply(Double.valueOf(-16789.2)).text); + assertEquals("-16,789 "+euro+" ", cfFR.apply(Double.valueOf(-16789.2)).text); // For zero, should be Space + currency symbol + spaces + Minus + spaces - // TODO + assertEquals(" $ - ", cfDft.apply(Double.valueOf(0)).text); + // TODO Fix the exception this incorrectly triggers + //assertEquals(" $ - ", cfUS.apply(Double.valueOf(0)).text); + // TODO Fix these to not have an incorrect bonus 0 on the end + //assertEquals(" "+pound+" - ", cfUK.apply(Double.valueOf(0)).text); + //assertEquals(" - "+euro+" ", cfFR.apply(Double.valueOf(0)).text); } } \ No newline at end of file diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestDataFormat.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestDataFormat.java index bbdc49935..0fd26ed7c 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestDataFormat.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestDataFormat.java @@ -131,4 +131,38 @@ public abstract class BaseTestDataFormat extends TestCase { exp3dp, fmt.formatCellValue(r.getCell(3), eval)); } } + + /** + * Localised accountancy formats + */ + public final void test58536() { + Workbook wb = _testDataProvider.createWorkbook(); + DataFormatter formatter = new DataFormatter(); + DataFormat fmt = wb.createDataFormat(); + Sheet sheet = wb.createSheet(); + Row r = sheet.createRow(0); + + char pound = '\u00A3'; + String formatUK = "_-[$"+pound+"-809]* #,##0_-;\\-[$"+pound+"-809]* #,##0_-;_-[$"+pound+"-809]* \"-\"??_-;_-@_-"; + + CellStyle cs = wb.createCellStyle(); + cs.setDataFormat(fmt.getFormat(formatUK)); + + Cell pve = r.createCell(0); + pve.setCellValue(12345); + pve.setCellStyle(cs); + + Cell nve = r.createCell(1); + nve.setCellValue(-12345); + nve.setCellStyle(cs); + + Cell zero = r.createCell(2); + zero.setCellValue(0); + zero.setCellStyle(cs); + + assertEquals(pound+" 12,345", formatter.formatCellValue(pve)); + assertEquals("-"+pound+" 12,345", formatter.formatCellValue(nve)); + // TODO Fix this to not have an extra 0 at the end + //assertEquals(pound+" - ", formatter.formatCellValue(zero)); + } }