* surround by brackets). </dl>
* <p/>
* A given format part may specify a given Locale, by including something
- * like <tt>[$$-409]</tt> or <tt>[$\u00A3-809]</tt> or <tt>[$-40C]</tt>. These
+ * like <tt>[$$-409]</tt> or <tt>[$£-809]</tt> or <tt>[$-40C]</tt>. These
* are (currently) largely ignored. You can use {@link DateFormatConverter}
* to look these up into Java Locales if desired.
* <p/>
* 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;
import org.apache.poi.hssf.util.HSSFColor;
import javax.swing.*;
+
import java.awt.*;
import java.util.Locale;
import java.util.Map;
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;
// 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]+)\\]";
// 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" +
"|\\[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
/**
* 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.
*/
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);
}
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':
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;
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 {
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();
}
@Test
- @Ignore("TODO") // TODO
public void testAccountingFormats() throws IOException {
char pound = '\u00A3';
char euro = '\u20AC';
// 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);
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(" 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);
- // TODO More
+ 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);
- // 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 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
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));
+ }
}