package org.simantics.spreadsheet; import java.util.ArrayList; import java.util.List; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.scl.runtime.tuple.Tuple; import org.simantics.spreadsheet.solver.SpreadsheetCell; public class Spreadsheets { public static final int SPREADSHEET_BTREE_SIZE = 100; public static final String CELL_TYPE_URI = "http://www.simantics.org/Spreadsheet-1.2/Cell"; public static final String LINE_TYPE_URI = "http://www.simantics.org/Spreadsheet-1.2/Line"; public static final String LINES_TYPE_URI = "http://www.simantics.org/Spreadsheet-1.2/Lines"; public static boolean asBoolean(Object object) { if(object instanceof Boolean) return (Boolean)object; else if(object instanceof Number) return ((Number)object).doubleValue() != 0; else if(object instanceof Variant) return asBoolean(((Variant)object).getValue()); else if(object instanceof String) { Double d = asDoubleWhereEmptyStringIsZero((String)object); if(d==null) return false; else return d != 0; } return false; } public static String asString(Object object) { if(object == null) return ""; if(object instanceof String) return (String)object; if(object instanceof Number) { double dVal = ((Number)object).doubleValue(); if(dVal == Math.floor(dVal)){ return ""+((Number)object).intValue(); } else { return object.toString(); } } else if(object instanceof Variant) { Object o = ((Variant) object).getValue(); if(o instanceof String) return (String)o; else if(o instanceof Number) asString((Number)o); else return o.toString(); } return object.toString(); } public static Double asDoubleWhereEmptyStringIsZero(Object object){ if(object instanceof Number) return ((Number)object).doubleValue(); else if(object instanceof String) { try { if(((String)object).isEmpty()) return 0.0; return Double.parseDouble((String)object); } catch (NumberFormatException e) { return null; } } else if(object instanceof Variant) { Object o = ((Variant) object).getValue(); return asDoubleWhereEmptyStringIsZero(o); } else if (SpreadsheetCell.EMPTY == object) { return null; } return null; } public static double asNumber(Object object) { if(object instanceof Number) { return ((Number)object).doubleValue(); } else if(object instanceof String) { try { String str = (String)object; if(str.isEmpty()) return 0; return Double.parseDouble((String)object); } catch (NumberFormatException e) { return 0; } } else if(object instanceof Variant) { Object o = ((Variant) object).getValue(); return asNumber(o); } else if (SpreadsheetCell.EMPTY == object) { return 0.0; } return 0.0; } public static Number asValidNumber(Object object) { if(object instanceof Number) { return (Number)object; } else if(object instanceof String) { try { return Double.parseDouble((String)object); } catch (NumberFormatException e) { return null; } } else if(object instanceof Variant) { Object o = ((Variant) object).getValue(); return asNumber(o); } else if (SpreadsheetCell.EMPTY == object) { return null; } return null; } public static boolean matchCriteria(Object value, Object criteria) { if(value==null || criteria==null) return false; if(value instanceof Variant){ Double dVal = asDoubleWhereEmptyStringIsZero(value); if(dVal==null) value = ((Variant)value).getValue(); else value = dVal; } if(criteria instanceof Variant){ Double dVal = asDoubleWhereEmptyStringIsZero(criteria); if(dVal==null) criteria = ((Variant)criteria).getValue(); else criteria = dVal; } if(criteria instanceof Number && value instanceof Number) { Number nc = (asNumber(criteria)); Number nv = (asNumber(value)); return nc.equals(nv); } if(criteria instanceof String){ boolean nums = false; Object valueObj = null; if(value instanceof Number){ valueObj = ((Number)value).doubleValue(); nums = true; } else valueObj = value.toString(); String sc = criteria.toString(); if(sc.length() >= 3){ String oper = sc.substring(0, 2); String criteriaStr = sc.substring(2); Double criteriaNum = null; try { criteriaNum = Double.parseDouble(criteriaStr); if(oper.equals("<>")){ if(!nums) return true; } else if(!nums) return false; nums = true; } catch (NumberFormatException e){ if(oper.equals("<>")){ if(nums) return true; } else if(nums) return false; nums = false; } if(oper.equals(">=")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) >= 0 ; else return ((Number)valueObj).doubleValue() >= criteriaNum; } else if(oper.equals("<=")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) <= 0 ; else return ((Number)valueObj).doubleValue() <= criteriaNum; } else if(oper.equals("<>")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) != 0 ; else return ((Number)valueObj).doubleValue() != criteriaNum; } } if(sc.length() >= 2){ String oper = sc.substring(0, 1); String criteriaStr = sc.substring(1); Double criteriaNum = null; try { criteriaNum = Double.parseDouble(criteriaStr); if(!nums) return false; nums = true; } catch (NumberFormatException e){ if(nums) return false; nums = false; } if(oper.equals("<")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) < 0; else return ((Number)valueObj).doubleValue() < criteriaNum; } else if(oper.equals(">")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) > 0; else return ((Number)valueObj).doubleValue() > criteriaNum; } else if(oper.equals("=")){ if(!nums) return (valueObj.toString().toLowerCase()).compareTo(criteriaStr.toLowerCase()) == 0; else return ((Number)valueObj).doubleValue() == criteriaNum; } } return sc.equals(valueObj); } else if (criteria instanceof Number){ return false; } throw new IllegalStateException(); } public static boolean excelEquals(Object left, Object right) { if(left instanceof String) { if(right instanceof String) { return ((String) left).toLowerCase().equals(((String) right).toLowerCase()); } } return left.equals(right); } // 1 kirjain, 'A' + column // 2 kirjainta, 'A' + column % 26 , 'A' + int((column-26)/26) // 3 kirjainta 'A' + column % 26 , 'A' + int(column-(26*(26+1)) / 26) % 26 // 0 26 // 26 26 + 26*26 // 26 + 26*26 26 + 26*26 + 26*26*26 public static String columnName(int column, int current, int limit, int chars) { if(column < limit) { char[] buf = new char[chars]; column -= current; for(int i=chars-1;i>=0;i--) { char rem = (char)(column % 26); column = (column / 26); buf[i] = (char)('A' + rem); } return new String(buf); } else return columnName(column, limit, 26*(limit+1), chars+1); } public static String columnName(int column) { return columnName(column, 0, 26, 1); } public static String cellName(int row, int column) { String result = columnName(column); result += (row+1); return result; } public static String offset(String location, int rowOffset, int columnOffset) { Range range = decodeCellAbsolute(location); String result = cellName(range.startRow + rowOffset, range.startColumn + columnOffset); // System.err.println("offset " + location + "(" + rowOffset + " " + columnOffset + ") = >" + result); return result; } public static Range decodeCellAbsolute(String identifier) { long l = decodeCellCoded(identifier); int row = (int)(l & 0xffffffff) - 1; int column = (int)((l>>32) & 0xffffffff); return new Range(row, row, column, column); } public static Range decodePossibleCellAbsolute(String identifier) { try { return decodeCellAbsolute(identifier); } catch (CellParseException e) { return null; } } public static long decodeCellCoded(String identifier) { // System.out.println("decodecellabsolute " + identifier); int row = 0; int column = 0; // identifier. int position = 0; // We skip $ here if(identifier.charAt(position) == '$') position++; int length = identifier.length(); while(position < length) { char b = identifier.charAt(position); if(b >= 'A' && b <= 'Z') column = column * 26 + (b-'A' + 1); else break; position++; } // We skip $ here if(position < length) if(identifier.charAt(position) == '$') position++; while(position < length) { char b = identifier.charAt(position); if(b >= '0' && b <= '9'){ row = row * 10 + b-'0'; } else if(b=='-' && position < (length-1)){//identify use of full row range here. position++; char b2 = identifier.charAt(position); if(b2=='1'){ row = 0; position++; break; } } else { break; } position++; } if(position == length) { // We need to be able to express -1 in row => report row + 1 here column--; // System.err.println("ra " + identifier + " => " + row + " " + column); return row + (((long)column)<<32); } else { throw new CellParseException("Cell identifier '" + identifier + "' is not a valid cell reference."); } } public static Range decodeCellRelative(String identifier, int row, int column) { int offset = Integer.valueOf(identifier.substring(1).trim()); // System.out.println("offset=" + offset); if(identifier.startsWith("L") || identifier.startsWith("l")) { return new Range(row, row, column-offset, column-offset); } else if(identifier.startsWith("R") || identifier.startsWith("r")) { return new Range(row, row, column+offset, column+offset); } else if(identifier.startsWith("U") || identifier.startsWith("u")) { return new Range(row-offset, row-offset, column, column); } else if(identifier.startsWith("D") || identifier.startsWith("d")) { return new Range(row+offset, row+offset, column, column); } else { throw new CellParseException("Relative cell syntax must begin with L|R|U|D."); } } public static Range decodeCell(String identifier, int row, int column) { if(identifier.startsWith("_")) { return decodeCellRelative(identifier.substring(1), row, column); } else { return decodeCellAbsolute(identifier); } } public static Range decodeReference(String identifier, int row, int column) { if(!identifier.startsWith("&")) throw new CellParseException("A reference cell was expected."); return decodeRange(identifier.substring(1), row, column); } public static List decodeRanges(String ranges){ String[] splitted = ranges.split(","); List result = new ArrayList<>(); for(String split : splitted){ result.add(decodeRange(split)); } return result; } public static Object extract(Object object, int row, int column) { if(object instanceof List) { @SuppressWarnings("rawtypes") List list = (List)object; if(list.size() <= row) return null; Object item = list.get(row); if(item instanceof Tuple) { Tuple tuple = (Tuple)item; if(tuple.length() <= column) return null; return tuple.get(column); } } return null; } public static int startRow(List ranges){ int s = -1; for(Range r : ranges){ if(r.startRow ranges){ int s = -1; for(Range r : ranges){ if(r.startColumnendRow){ endRow = r.endRow; } return endRow - startRow +1; } public static int amountOfColumns(Range r){ int endColumn = -2; int startColumn = -2; if(r.isFullColumns()){ return Range.MAXCOLUMNSPEC; } if(endColumn == -2 && startColumn == -2){ endColumn = r.endColumn; startColumn = r.startColumn; } if(r.startColumnendColumn){ endColumn = r.endColumn; } return endColumn - startColumn +1; } public static Range decodeRange(String rangeOrCell) { if(rangeOrCell.isEmpty()) return fullRange(); return decodeRange(rangeOrCell, 0, 0); } public static Range fullRange() { return new Range(0, -1, 0, -1); } public static Range decodeRange(String rangeOrCell, int row, int column) { String[] parts = rangeOrCell.split(":"); if(parts.length == 1) { return decodeCell(rangeOrCell, row, column); } else if (parts.length == 2) { Range from = decodeCell(parts[0].trim(), row, column); // System.out.println("decodefrom=" + from); Range to = decodeCell(parts[1].trim(), row, column); // System.out.println("decodeto=" + to); return Range.combine(from, to); } else { throw new CellParseException("The reference cell syntax was invalid. At most 1 occurrence of ':' is expected."); } } }