package jxl.biff; import java.text.DecimalFormat; import java.text.MessageFormat; import java.util.Collection; import java.util.Iterator; import jxl.WorkbookSettings; import jxl.biff.formula.ExternalSheet; import jxl.biff.formula.FormulaException; import jxl.biff.formula.FormulaParser; import jxl.biff.formula.ParseContext; import jxl.common.Assert; import jxl.common.Logger; public class DVParser { private static Logger logger = Logger.getLogger(DVParser.class); public static class DVType { private int value; private String desc; private static DVType[] types = new DVType[0]; DVType(int v, String d) { this.value = v; this.desc = d; DVType[] oldtypes = types; types = new DVType[oldtypes.length + 1]; System.arraycopy(oldtypes, 0, types, 0, oldtypes.length); types[oldtypes.length] = this; } static DVType getType(int v) { DVType found = null; for (int i = 0; i < types.length && found == null; i++) { if ((types[i]).value == v) found = types[i]; } return found; } public int getValue() { return this.value; } public String getDescription() { return this.desc; } } public static class ErrorStyle { private int value; private static ErrorStyle[] types = new ErrorStyle[0]; ErrorStyle(int v) { this.value = v; ErrorStyle[] oldtypes = types; types = new ErrorStyle[oldtypes.length + 1]; System.arraycopy(oldtypes, 0, types, 0, oldtypes.length); types[oldtypes.length] = this; } static ErrorStyle getErrorStyle(int v) { ErrorStyle found = null; for (int i = 0; i < types.length && found == null; i++) { if ((types[i]).value == v) found = types[i]; } return found; } public int getValue() { return this.value; } } public static class Condition { private int value; private MessageFormat format; private static Condition[] types = new Condition[0]; Condition(int v, String pattern) { this.value = v; this.format = new MessageFormat(pattern); Condition[] oldtypes = types; types = new Condition[oldtypes.length + 1]; System.arraycopy(oldtypes, 0, types, 0, oldtypes.length); types[oldtypes.length] = this; } static Condition getCondition(int v) { Condition found = null; for (int i = 0; i < types.length && found == null; i++) { if ((types[i]).value == v) found = types[i]; } return found; } public int getValue() { return this.value; } public String getConditionString(String s1, String s2) { return this.format.format(new String[] { s1, s2 }); } } public static final DVType ANY = new DVType(0, "any"); public static final DVType INTEGER = new DVType(1, "int"); public static final DVType DECIMAL = new DVType(2, "dec"); public static final DVType LIST = new DVType(3, "list"); public static final DVType DATE = new DVType(4, "date"); public static final DVType TIME = new DVType(5, "time"); public static final DVType TEXT_LENGTH = new DVType(6, "strlen"); public static final DVType FORMULA = new DVType(7, "form"); public static final ErrorStyle STOP = new ErrorStyle(0); public static final ErrorStyle WARNING = new ErrorStyle(1); public static final ErrorStyle INFO = new ErrorStyle(2); public static final Condition BETWEEN = new Condition(0, "{0} <= x <= {1}"); public static final Condition NOT_BETWEEN = new Condition(1, "!({0} <= x <= {1}"); public static final Condition EQUAL = new Condition(2, "x == {0}"); public static final Condition NOT_EQUAL = new Condition(3, "x != {0}"); public static final Condition GREATER_THAN = new Condition(4, "x > {0}"); public static final Condition LESS_THAN = new Condition(5, "x < {0}"); public static final Condition GREATER_EQUAL = new Condition(6, "x >= {0}"); public static final Condition LESS_EQUAL = new Condition(7, "x <= {0}"); private static final int STRING_LIST_GIVEN_MASK = 128; private static final int EMPTY_CELLS_ALLOWED_MASK = 256; private static final int SUPPRESS_ARROW_MASK = 512; private static final int SHOW_PROMPT_MASK = 262144; private static final int SHOW_ERROR_MASK = 524288; private static DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#"); private static final int MAX_VALIDATION_LIST_LENGTH = 254; private static final int MAX_ROWS = 65535; private static final int MAX_COLUMNS = 255; private DVType type; private ErrorStyle errorStyle; private Condition condition; private boolean stringListGiven; private boolean emptyCellsAllowed; private boolean suppressArrow; private boolean showPrompt; private boolean showError; private String promptTitle; private String errorTitle; private String promptText; private String errorText; private FormulaParser formula1; private String formula1String; private FormulaParser formula2; private String formula2String; private int column1; private int row1; private int column2; private int row2; private boolean extendedCellsValidation; private boolean copied; public DVParser(byte[] data, ExternalSheet es, WorkbookMethods nt, WorkbookSettings ws) { Assert.verify((nt != null)); this.copied = false; int options = IntegerHelper.getInt(data[0], data[1], data[2], data[3]); int typeVal = options & 0xF; this.type = DVType.getType(typeVal); int errorStyleVal = (options & 0x70) >> 4; this.errorStyle = ErrorStyle.getErrorStyle(errorStyleVal); int conditionVal = (options & 0xF00000) >> 20; this.condition = Condition.getCondition(conditionVal); this.stringListGiven = ((options & 0x80) != 0); this.emptyCellsAllowed = ((options & 0x100) != 0); this.suppressArrow = ((options & 0x200) != 0); this.showPrompt = ((options & 0x40000) != 0); this.showError = ((options & 0x80000) != 0); int pos = 4; int length = IntegerHelper.getInt(data[pos], data[pos + 1]); if (length > 0 && data[pos + 2] == 0) { this.promptTitle = StringHelper.getString(data, length, pos + 3, ws); pos += length + 3; } else if (length > 0) { this.promptTitle = StringHelper.getUnicodeString(data, length, pos + 3); pos += length * 2 + 3; } else { pos += 3; } length = IntegerHelper.getInt(data[pos], data[pos + 1]); if (length > 0 && data[pos + 2] == 0) { this.errorTitle = StringHelper.getString(data, length, pos + 3, ws); pos += length + 3; } else if (length > 0) { this.errorTitle = StringHelper.getUnicodeString(data, length, pos + 3); pos += length * 2 + 3; } else { pos += 3; } length = IntegerHelper.getInt(data[pos], data[pos + 1]); if (length > 0 && data[pos + 2] == 0) { this.promptText = StringHelper.getString(data, length, pos + 3, ws); pos += length + 3; } else if (length > 0) { this.promptText = StringHelper.getUnicodeString(data, length, pos + 3); pos += length * 2 + 3; } else { pos += 3; } length = IntegerHelper.getInt(data[pos], data[pos + 1]); if (length > 0 && data[pos + 2] == 0) { this.errorText = StringHelper.getString(data, length, pos + 3, ws); pos += length + 3; } else if (length > 0) { this.errorText = StringHelper.getUnicodeString(data, length, pos + 3); pos += length * 2 + 3; } else { pos += 3; } int formula1Length = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 4; int formula1Pos = pos; pos += formula1Length; int formula2Length = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 4; int formula2Pos = pos; pos += formula2Length; pos += 2; this.row1 = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 2; this.row2 = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 2; this.column1 = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 2; this.column2 = IntegerHelper.getInt(data[pos], data[pos + 1]); pos += 2; this.extendedCellsValidation = !(this.row1 == this.row2 && this.column1 == this.column2); try { EmptyCell tmprt = new EmptyCell(this.column1, this.row1); if (formula1Length != 0) { byte[] tokens = new byte[formula1Length]; System.arraycopy(data, formula1Pos, tokens, 0, formula1Length); this.formula1 = new FormulaParser(tokens, tmprt, es, nt, ws, ParseContext.DATA_VALIDATION); this.formula1.parse(); } if (formula2Length != 0) { byte[] tokens = new byte[formula2Length]; System.arraycopy(data, formula2Pos, tokens, 0, formula2Length); this.formula2 = new FormulaParser(tokens, tmprt, es, nt, ws, ParseContext.DATA_VALIDATION); this.formula2.parse(); } } catch (FormulaException e) { logger.warn(e.getMessage() + " for cells " + CellReferenceHelper.getCellReference(this.column1, this.row1) + "-" + CellReferenceHelper.getCellReference(this.column2, this.row2)); } } public DVParser(Collection strings) { this.copied = false; this.type = LIST; this.errorStyle = STOP; this.condition = BETWEEN; this.extendedCellsValidation = false; this.stringListGiven = true; this.emptyCellsAllowed = true; this.suppressArrow = false; this.showPrompt = true; this.showError = true; this.promptTitle = "\000"; this.errorTitle = "\000"; this.promptText = "\000"; this.errorText = "\000"; if (strings.size() == 0) logger.warn("no validation strings - ignoring"); Iterator i = strings.iterator(); StringBuffer formulaString = new StringBuffer(); formulaString.append(i.next().toString()); while (i.hasNext()) { formulaString.append('\000'); formulaString.append(' '); formulaString.append(i.next().toString()); } if (formulaString.length() > 254) { logger.warn("Validation list exceeds maximum number of characters - truncating"); formulaString.delete(254, formulaString.length()); } formulaString.insert(0, '"'); formulaString.append('"'); this.formula1String = formulaString.toString(); } public DVParser(String namedRange) { if (namedRange.length() == 0) { this.copied = false; this.type = FORMULA; this.errorStyle = STOP; this.condition = EQUAL; this.extendedCellsValidation = false; this.stringListGiven = false; this.emptyCellsAllowed = false; this.suppressArrow = false; this.showPrompt = true; this.showError = true; this.promptTitle = "\000"; this.errorTitle = "\000"; this.promptText = "\000"; this.errorText = "\000"; this.formula1String = "\"\""; return; } this.copied = false; this.type = LIST; this.errorStyle = STOP; this.condition = BETWEEN; this.extendedCellsValidation = false; this.stringListGiven = false; this.emptyCellsAllowed = true; this.suppressArrow = false; this.showPrompt = true; this.showError = true; this.promptTitle = "\000"; this.errorTitle = "\000"; this.promptText = "\000"; this.errorText = "\000"; this.formula1String = namedRange; } public DVParser(int c1, int r1, int c2, int r2) { this.copied = false; this.type = LIST; this.errorStyle = STOP; this.condition = BETWEEN; this.extendedCellsValidation = false; this.stringListGiven = false; this.emptyCellsAllowed = true; this.suppressArrow = false; this.showPrompt = true; this.showError = true; this.promptTitle = "\000"; this.errorTitle = "\000"; this.promptText = "\000"; this.errorText = "\000"; StringBuffer formulaString = new StringBuffer(); CellReferenceHelper.getCellReference(c1, r1, formulaString); formulaString.append(':'); CellReferenceHelper.getCellReference(c2, r2, formulaString); this.formula1String = formulaString.toString(); } public DVParser(double val1, double val2, Condition c) { this.copied = false; this.type = DECIMAL; this.errorStyle = STOP; this.condition = c; this.extendedCellsValidation = false; this.stringListGiven = false; this.emptyCellsAllowed = true; this.suppressArrow = false; this.showPrompt = true; this.showError = true; this.promptTitle = "\000"; this.errorTitle = "\000"; this.promptText = "\000"; this.errorText = "\000"; this.formula1String = DECIMAL_FORMAT.format(val1); if (!Double.isNaN(val2)) this.formula2String = DECIMAL_FORMAT.format(val2); } public DVParser(DVParser copy) { this.copied = true; this.type = copy.type; this.errorStyle = copy.errorStyle; this.condition = copy.condition; this.stringListGiven = copy.stringListGiven; this.emptyCellsAllowed = copy.emptyCellsAllowed; this.suppressArrow = copy.suppressArrow; this.showPrompt = copy.showPrompt; this.showError = copy.showError; this.promptTitle = copy.promptTitle; this.promptText = copy.promptText; this.errorTitle = copy.errorTitle; this.errorText = copy.errorText; this.extendedCellsValidation = copy.extendedCellsValidation; this.row1 = copy.row1; this.row2 = copy.row2; this.column1 = copy.column1; this.column2 = copy.column2; if (copy.formula1String != null) { this.formula1String = copy.formula1String; this.formula2String = copy.formula2String; } else { try { this.formula1String = copy.formula1.getFormula(); this.formula2String = (copy.formula2 != null) ? copy.formula2.getFormula() : null; } catch (FormulaException e) { logger.warn("Cannot parse validation formula: " + e.getMessage()); } } } public byte[] getData() { byte[] f1Bytes = (this.formula1 != null) ? this.formula1.getBytes() : new byte[0]; byte[] f2Bytes = (this.formula2 != null) ? this.formula2.getBytes() : new byte[0]; int dataLength = 4 + this.promptTitle.length() * 2 + 3 + this.errorTitle.length() * 2 + 3 + this.promptText.length() * 2 + 3 + this.errorText.length() * 2 + 3 + f1Bytes.length + 2 + f2Bytes.length + 2 + 4 + 10; byte[] data = new byte[dataLength]; int pos = 0; int options = 0; options |= this.type.getValue(); options |= this.errorStyle.getValue() << 4; options |= this.condition.getValue() << 20; if (this.stringListGiven) options |= 0x80; if (this.emptyCellsAllowed) options |= 0x100; if (this.suppressArrow) options |= 0x200; if (this.showPrompt) options |= 0x40000; if (this.showError) options |= 0x80000; IntegerHelper.getFourBytes(options, data, pos); pos += 4; IntegerHelper.getTwoBytes(this.promptTitle.length(), data, pos); pos += 2; data[pos] = 1; pos++; StringHelper.getUnicodeBytes(this.promptTitle, data, pos); pos += this.promptTitle.length() * 2; IntegerHelper.getTwoBytes(this.errorTitle.length(), data, pos); pos += 2; data[pos] = 1; pos++; StringHelper.getUnicodeBytes(this.errorTitle, data, pos); pos += this.errorTitle.length() * 2; IntegerHelper.getTwoBytes(this.promptText.length(), data, pos); pos += 2; data[pos] = 1; pos++; StringHelper.getUnicodeBytes(this.promptText, data, pos); pos += this.promptText.length() * 2; IntegerHelper.getTwoBytes(this.errorText.length(), data, pos); pos += 2; data[pos] = 1; pos++; StringHelper.getUnicodeBytes(this.errorText, data, pos); pos += this.errorText.length() * 2; IntegerHelper.getTwoBytes(f1Bytes.length, data, pos); pos += 4; System.arraycopy(f1Bytes, 0, data, pos, f1Bytes.length); pos += f1Bytes.length; IntegerHelper.getTwoBytes(f2Bytes.length, data, pos); pos += 4; System.arraycopy(f2Bytes, 0, data, pos, f2Bytes.length); pos += f2Bytes.length; IntegerHelper.getTwoBytes(1, data, pos); pos += 2; IntegerHelper.getTwoBytes(this.row1, data, pos); pos += 2; IntegerHelper.getTwoBytes(this.row2, data, pos); pos += 2; IntegerHelper.getTwoBytes(this.column1, data, pos); pos += 2; IntegerHelper.getTwoBytes(this.column2, data, pos); pos += 2; return data; } public void insertRow(int row) { if (this.formula1 != null) this.formula1.rowInserted(0, row, true); if (this.formula2 != null) this.formula2.rowInserted(0, row, true); if (this.row1 >= row) this.row1++; if (this.row2 >= row && this.row2 != 65535) this.row2++; } public void insertColumn(int col) { if (this.formula1 != null) this.formula1.columnInserted(0, col, true); if (this.formula2 != null) this.formula2.columnInserted(0, col, true); if (this.column1 >= col) this.column1++; if (this.column2 >= col && this.column2 != 255) this.column2++; } public void removeRow(int row) { if (this.formula1 != null) this.formula1.rowRemoved(0, row, true); if (this.formula2 != null) this.formula2.rowRemoved(0, row, true); if (this.row1 > row) this.row1--; if (this.row2 >= row) this.row2--; } public void removeColumn(int col) { if (this.formula1 != null) this.formula1.columnRemoved(0, col, true); if (this.formula2 != null) this.formula2.columnRemoved(0, col, true); if (this.column1 > col) this.column1--; if (this.column2 >= col && this.column2 != 255) this.column2--; } public int getFirstColumn() { return this.column1; } public int getLastColumn() { return this.column2; } public int getFirstRow() { return this.row1; } public int getLastRow() { return this.row2; } String getValidationFormula() throws FormulaException { if (this.type == LIST) return this.formula1.getFormula(); String s1 = this.formula1.getFormula(); String s2 = (this.formula2 != null) ? this.formula2.getFormula() : null; return this.condition.getConditionString(s1, s2) + "; x " + this.type.getDescription(); } public void setCell(int col, int row, ExternalSheet es, WorkbookMethods nt, WorkbookSettings ws) throws FormulaException { if (this.extendedCellsValidation) return; this.row1 = row; this.row2 = row; this.column1 = col; this.column2 = col; this.formula1 = new FormulaParser(this.formula1String, es, nt, ws, ParseContext.DATA_VALIDATION); this.formula1.parse(); if (this.formula2String != null) { this.formula2 = new FormulaParser(this.formula2String, es, nt, ws, ParseContext.DATA_VALIDATION); this.formula2.parse(); } } public void extendCellValidation(int cols, int rows) { this.row2 = this.row1 + rows; this.column2 = this.column1 + cols; this.extendedCellsValidation = true; } public boolean extendedCellsValidation() { return this.extendedCellsValidation; } public boolean copied() { return this.copied; } }