]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.spreadsheet.common/src/org/simantics/spreadsheet/util/SpreadsheetUtils.java
Adopt spreadsheet changes made in Balas development
[simantics/platform.git] / bundles / org.simantics.spreadsheet.common / src / org / simantics / spreadsheet / util / SpreadsheetUtils.java
index c70a9aa8d089fe7290fee4e387a6830c49bf6344..a784ccf9efaf340dd4834107a5dfd51e5e0cf5c9 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.spreadsheet.util;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.List;\r
-\r
-import org.apache.poi.ss.usermodel.DataFormatter;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.binding.error.BindingException;\r
-import org.simantics.databoard.binding.mutable.MutableVariant;\r
-import org.simantics.databoard.binding.mutable.Variant;\r
-import org.simantics.datatypes.utils.BTree;\r
-import org.simantics.datatypes.utils.BTreeUtils;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.PossibleChild;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.Logger;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.exception.VariableException;\r
-import org.simantics.db.layer0.util.Layer0Utils;\r
-import org.simantics.db.layer0.variable.ProxyVariables;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.layer0.variable.VariableSpaceManipulator.PropertyCreationData;\r
-import org.simantics.db.layer0.variable.Variables;\r
-import org.simantics.db.request.Write;\r
-import org.simantics.db.service.SerialisationSupport;\r
-import org.simantics.document.server.io.IColor;\r
-import org.simantics.document.server.io.IFont;\r
-import org.simantics.document.server.io.RGBColor;\r
-import org.simantics.document.server.io.SimpleFont;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.scl.runtime.function.Function1;\r
-import org.simantics.scl.runtime.tuple.Tuple;\r
-import org.simantics.spreadsheet.CellEditor;\r
-import org.simantics.spreadsheet.CellEditor.Transaction;\r
-import org.simantics.spreadsheet.ClientModel.OperationMode;\r
-import org.simantics.spreadsheet.ClientModel;\r
-import org.simantics.spreadsheet.Range;\r
-import org.simantics.spreadsheet.common.TableCell;\r
-import org.simantics.spreadsheet.common.cell.StringCellParser;\r
-import org.simantics.spreadsheet.common.exception.CellParseException;\r
-import org.simantics.spreadsheet.resource.SpreadsheetResource;\r
-import org.simantics.utils.datastructures.Pair;\r
-\r
-public class SpreadsheetUtils {\r
-    \r
-    public static final int SPREADSHEET_BTREE_SIZE = 100;\r
-       \r
-       public static String offset(String location, int rowOffset, int columnOffset) {\r
-\r
-               Range range = decodeCellAbsolute(location);\r
-               String result = cellName(range.startRow + rowOffset, range.startColumn + columnOffset); \r
-               //      System.err.println("offset " + location + "(" + rowOffset + " " + columnOffset + ") = >" + result);\r
-               return result;\r
-\r
-       }\r
-\r
-       public static Object extract(Object object, int row, int column) {\r
-               if(object instanceof List) {\r
-                       List list = (List)object;\r
-                       if(list.size() <= row) return null;\r
-                       Object item = list.get(row);\r
-                       if(item instanceof Tuple) {\r
-                               Tuple tuple = (Tuple)item;\r
-                               if(tuple.length() <= column) return null;\r
-                               return tuple.get(column);\r
-                       }\r
-               }\r
-               return null;\r
-       }\r
-\r
-       //      1 kirjain, 'A' + column\r
-       //      2 kirjainta, 'A' + column % 26 , 'A' + int((column-26)/26)\r
-       //      3 kirjainta 'A' + column % 26 , 'A' + int(column-(26*(26+1)) / 26) % 26\r
-\r
-       //    0              26\r
-       //    26             26 + 26*26\r
-       //    26 + 26*26     26 + 26*26 + 26*26*26\r
-\r
-       public static String columnName(int column, int current, int limit, int chars) {\r
-\r
-               if(column < limit) {\r
-\r
-                       char[] buf = new char[chars];\r
-                       column -= current;\r
-                       for(int i=chars-1;i>=0;i--) {\r
-                               char rem = (char)(column % 26);\r
-                               column = (column / 26);\r
-                               buf[i] = (char)('A' + rem); \r
-                       }\r
-                       return new String(buf);\r
-\r
-               } else return columnName(column, limit, 26*(limit+1), chars+1);\r
-\r
-       }\r
-\r
-       public static String columnName(int column) {\r
-               return columnName(column, 0, 26, 1);\r
-       }    \r
-\r
-       public static String cellName(int row, int column) {\r
-\r
-               String result = columnName(column);\r
-               result += (row+1);\r
-               return result;\r
-\r
-       }    \r
-\r
-       public static Range decodeCellAbsolute(String identifier) {\r
-               long l = decodeCellCoded(identifier);\r
-               int row = (int)(l & 0xffffffff) - 1;\r
-               int column = (int)((l>>32) & 0xffffffff);\r
-               return new Range(row, row, column, column);\r
-       }\r
-\r
-       public static Range decodePossibleCellAbsolute(String identifier) {\r
-           try {\r
-               return decodeCellAbsolute(identifier);\r
-           } catch (CellParseException e) {\r
-               return null;\r
-           }\r
-       }\r
-\r
-       public static long decodeCellCoded(String identifier) {\r
-\r
-           //        System.out.println("decodecellabsolute " + identifier);\r
-\r
-           int row = 0;\r
-               int column = 0;\r
-               \r
-//             identifier.\r
-               \r
-               int position = 0;\r
-               \r
-               // We skip $ here\r
-               if(identifier.charAt(position) == '$') position++;\r
-               \r
-               int length = identifier.length();\r
-               \r
-               while(position < length) {\r
-                       char b = identifier.charAt(position);\r
-                       if(b >= 'A' && b <= 'Z') column = column * 26 + (b-'A' + 1);\r
-                       else break;\r
-                       position++;\r
-               }\r
-\r
-               // We skip $ here\r
-               if(position < length)\r
-                       if(identifier.charAt(position) == '$')\r
-                               position++;\r
-\r
-               while(position < length) {\r
-                       char b = identifier.charAt(position);\r
-                       if(b >= '0' && b <= '9'){\r
-                               row = row * 10 + b-'0';\r
-                       }\r
-                       else if(b=='-' && position < (length-1)){//identify use of full row range here.\r
-                               position++;\r
-                               char b2 = identifier.charAt(position);\r
-                               if(b2=='1'){\r
-                                       row = 0;\r
-                                       position++;\r
-                                       break;\r
-                               }\r
-                       }\r
-                       else {\r
-                               break;\r
-                       }\r
-                       position++;\r
-               }\r
-\r
-               if(position == length) {\r
-\r
-                       // We need to be able to express -1 in row => report row + 1 here\r
-                       column--;\r
-                       //            System.err.println("ra " + identifier + " => " + row + " " + column);\r
-                       return row + (((long)column)<<32);\r
-\r
-               } else {\r
-\r
-                       throw new CellParseException("Cell identifier '" + identifier + "' is not a valid cell reference.");\r
-\r
-               }\r
-\r
-       }\r
-\r
-       public static Range decodeCellRelative(String identifier, int row, int column) {\r
-\r
-               int offset = Integer.valueOf(identifier.substring(1).trim());\r
-               //        System.out.println("offset=" + offset);\r
-\r
-               if(identifier.startsWith("L") || identifier.startsWith("l")) {\r
-                       return new Range(row, row, column-offset, column-offset);\r
-               } else if(identifier.startsWith("R") || identifier.startsWith("r")) {\r
-                       return new Range(row, row, column+offset, column+offset);\r
-               } else if(identifier.startsWith("U") || identifier.startsWith("u")) {\r
-                       return new Range(row-offset, row-offset, column, column);\r
-               } else if(identifier.startsWith("D") || identifier.startsWith("d")) {\r
-                       return new Range(row+offset, row+offset, column, column);\r
-               } else {\r
-                       throw new CellParseException("Relative cell syntax must begin with L|R|U|D.");\r
-               }\r
-\r
-       }\r
-\r
-       public static Range decodeCell(String identifier, int row, int column) {\r
-\r
-               if(identifier.startsWith("_")) {\r
-                       return decodeCellRelative(identifier.substring(1), row, column);\r
-               } else {\r
-                       return decodeCellAbsolute(identifier);\r
-               }\r
-\r
-       }\r
-\r
-       public static Range decodeReference(String identifier, int row, int column) {\r
-               if(!identifier.startsWith("&")) throw new CellParseException("A reference cell was expected.");\r
-               return decodeRange(identifier.substring(1), row, column);\r
-       }\r
-\r
-       public static List<Range> decodeRanges(String ranges){\r
-               String[] splitted = ranges.split(",");\r
-               List<Range> result = new ArrayList<>();\r
-               for(String split : splitted){\r
-                       result.add(decodeRange(split));\r
-               }\r
-               return result;\r
-       }\r
-       \r
-       public static int startRow(List<Range> ranges){\r
-               int s = -1;\r
-               for(Range r : ranges){\r
-                       if(r.startRow<s || s==-1){\r
-                               s = r.startRow;\r
-                       }\r
-               }\r
-               return s;\r
-       }\r
-       \r
-       public static int startColumn(List<Range> ranges){\r
-               int s = -1;\r
-               for(Range r : ranges){\r
-                       if(r.startColumn<s || s==-1){\r
-                               s = r.startColumn;\r
-                       }\r
-               }\r
-               return s;\r
-       }\r
-       \r
-       public static int amountOfRows(Range r){\r
-               int endRow = -2;\r
-               int startRow = -2;\r
-               if(r.isFullRows()){\r
-                       return Range.MAXROWSPEC;\r
-               }\r
-               if(endRow == -2 && startRow == -2){\r
-                       endRow = r.endRow;\r
-                       startRow = r.startRow;\r
-               }\r
-               if(r.startRow<startRow){\r
-                       startRow = r.startRow;\r
-               }\r
-               if(r.endRow>endRow){\r
-                       endRow = r.endRow;\r
-               }\r
-               return endRow - startRow +1;\r
-       }\r
-       \r
-       public static int amountOfColumns(Range r){\r
-               int endColumn = -2;\r
-               int startColumn = -2;\r
-               if(r.isFullColumns()){\r
-                       return Range.MAXCOLUMNSPEC;\r
-               }\r
-               if(endColumn == -2 && startColumn == -2){\r
-                       endColumn = r.endColumn; \r
-                       startColumn = r.startColumn;\r
-               }\r
-               if(r.startColumn<startColumn){\r
-                       startColumn = r.startColumn;\r
-               }\r
-               if(r.endColumn>endColumn){\r
-                       endColumn = r.endColumn;\r
-               }\r
-               return endColumn - startColumn +1;\r
-       }\r
-       \r
-       public static Range decodeRange(String rangeOrCell) {\r
-               if(rangeOrCell.isEmpty()) return fullRange();\r
-               return decodeRange(rangeOrCell, 0, 0);\r
-       }\r
-       \r
-       public static Range fullRange() {\r
-               return new Range(0, -1, 0, -1);\r
-       }\r
-\r
-       public static Range decodeRange(String rangeOrCell, int row, int column) {\r
-       \r
-               String[] parts = rangeOrCell.split(":");\r
-               if(parts.length == 1) {\r
-\r
-                       return decodeCell(rangeOrCell, row, column);\r
-\r
-               } else if (parts.length == 2) {\r
-\r
-                       Range from = decodeCell(parts[0].trim(), row, column);\r
-                       //            System.out.println("decodefrom=" + from);\r
-                       Range to = decodeCell(parts[1].trim(), row, column);\r
-                       //            System.out.println("decodeto=" + to);\r
-                       return Range.combine(from, to);\r
-\r
-               } else {\r
-\r
-                       throw new CellParseException("The reference cell syntax was invalid. At most 1 occurrence of ':' is expected.");\r
-\r
-               }\r
-\r
-       }\r
-\r
-       public static Pair<String, Collection<PropertyCreationData>> parse(String text, StringCellParser[] parsers) {\r
-\r
-               try {\r
-\r
-                       for(StringCellParser parser : parsers) { \r
-                               Collection<PropertyCreationData> parsed = parser.parse(text);\r
-                               if(parsed != null) return Pair.make(parser.getType(), parsed);\r
-                       }\r
-\r
-               } catch (Throwable t) {\r
-                       t.printStackTrace();\r
-               }\r
-\r
-               return null;\r
-\r
-       }\r
-\r
-       public static boolean isImmutable(Object object) {\r
-               return !(object instanceof Resource) && !(object instanceof Variable); \r
-       }\r
-\r
-       public static String getLabel(ReadGraph graph, Object object) throws DatabaseException {\r
-\r
-               if(object == null) {\r
-                       return "no data";\r
-               }\r
-\r
-               if(object instanceof Resource) {\r
-                       return NameUtils.getSafeName(graph, (Resource)object);\r
-               } else if (object instanceof Variable) {\r
-                       try {\r
-                               Object value = ((Variable)object).getValue(graph);\r
-                               return value.toString();\r
-                               //return toString(value);\r
-                       } catch (VariableException e) {\r
-                               Object value = ((Variable)object).getPropertyValue(graph, "Label"); \r
-                               return value.toString();\r
-                       }\r
-               } else if (object instanceof double[]) {\r
-                       return object.toString();\r
-                       //                      return toString(object); \r
-               } else {\r
-                       return object.toString();\r
-               }\r
-\r
-       }\r
-\r
-       private static String toString(Object object) {\r
-               if(object instanceof double[]) {\r
-                       try {\r
-                               return Bindings.DOUBLE_ARRAY.toString(object);\r
-                       } catch (BindingException e) {\r
-                               return object.toString();\r
-                       }\r
-               } else {\r
-                       return object.toString();\r
-               }\r
-       }\r
-\r
-       public static String getContent(ReadGraph graph, Object object) throws DatabaseException {\r
-\r
-               if(object == null) {\r
-                       return null;\r
-               }\r
-\r
-               if(object instanceof Resource) {\r
-                       SerialisationSupport support = graph.getService(SerialisationSupport.class);\r
-                       return support.getResourceSerializer().createRandomAccessId((Resource)object);\r
-               } else if (object instanceof Variable) {\r
-                       return ((Variable)object).getURI(graph);\r
-               } else {\r
-                       return "";\r
-               }\r
-\r
-       }\r
-\r
-       public static void main(String[] args) {\r
-               for(int i=0;i<16384;i++) {\r
-                       String name = columnName(i);\r
-                       Range r = decodeCellAbsolute(name + "1");\r
-                       System.err.println(i + " " + name + " " + r);\r
-               }\r
-       }\r
-\r
-       public static String getLabel(ClientModel model, int row, int column) {\r
-               try {\r
-                       String location = SpreadsheetUtils.cellName(row, column);\r
-                       String label = model.getPropertyAt(location, ClientModel.LABEL);\r
-                       if(label != null) return label;\r
-                       Variant content = SpreadsheetUtils.getSafeClientVariant(model, location, ClientModel.CONTENT);\r
-                       if(content != null) return SpreadsheetUtils.getContentString(content);\r
-                       else return null;\r
-               } catch (Throwable e) {\r
-                       e.printStackTrace();\r
-                       return null;\r
-               }\r
-       }\r
-\r
-       public static String getContentString(Variant content) {\r
-               return content.getValue().toString();\r
-       }\r
-\r
-       public static boolean isInBounds(String base, String location, int wBounds, int hBounds) {\r
-               Range baseRange = decodeCellAbsolute(base);\r
-               Range locationRange = decodeCellAbsolute(location);\r
-               if(locationRange.startColumn < baseRange.startColumn) return false;\r
-               if(locationRange.startRow < baseRange.startRow) return false;\r
-               int wb = wBounds == -1 ? (Integer.MAX_VALUE / 3) : wBounds;\r
-               int hb = hBounds == -1 ? (Integer.MAX_VALUE / 3) : hBounds;\r
-               if(locationRange.startColumn > (baseRange.startColumn+wb-1)) return false;\r
-               if(locationRange.startRow > (baseRange.startRow+hb-1)) return false;\r
-               return true;\r
-       }\r
-\r
-       public static void schedule(CellEditor.Transaction<?> transaction, Write write) {\r
-\r
-               if(transaction == null) {\r
-\r
-                       TransactionImpl impl = (TransactionImpl)startTransaction(OperationMode.OPERATION);\r
-                       impl.add(write);\r
-                       impl.commit();\r
-\r
-               } else {\r
-\r
-                       TransactionImpl impl = (TransactionImpl)transaction;\r
-                       impl.add(write);\r
-\r
-               }\r
-\r
-       }\r
-       \r
-       public static Transaction<Write> startTransaction() {\r
-               return startTransaction(OperationMode.EDIT_MODE);\r
-       }\r
-\r
-       public static Transaction<Write> startTransaction(OperationMode mode) {\r
-               return new TransactionImpl(mode);\r
-       }       \r
-\r
-       static class TransactionImpl implements CellEditor.Transaction<Write> {\r
-\r
-               private ArrayList<Write> writes = new ArrayList<>();\r
-               private final OperationMode mode;\r
-        private Object context;\r
-        private List<Object> needSync;\r
-               \r
-               public TransactionImpl(OperationMode mode) {\r
-                   this.mode = mode;\r
-        }\r
-\r
-               public void commit() {\r
-\r
-                       Simantics.async(new WriteRequest() {\r
-\r
-                               @Override\r
-                               public void perform(WriteGraph graph) throws DatabaseException {\r
-                           graph.markUndoPoint();\r
-                           for(int i=0;i<writes.size();i++) {\r
-                               Write write = writes.get(i);\r
-                               try {\r
-                                   write.perform(graph);\r
-                               } catch (DatabaseException e) {\r
-                                   e.printStackTrace();\r
-                                   Logger.defaultLogError(e);\r
-                               }\r
-                               // This can schedule more writes\r
-                               //graph.syncRequest(write);\r
-                           }\r
-                           writes.clear();\r
-                               }\r
-                       });\r
-               }\r
-\r
-               @Override\r
-               public void add(Write write) {\r
-                       writes.add(write);\r
-               }\r
-\r
-        @Override\r
-        public boolean isOperationMode() {\r
-            return mode.equals(OperationMode.OPERATION);\r
-        }\r
-\r
-        @Override\r
-        public void setContext(Object context) {\r
-            this.context = context;\r
-        }\r
-\r
-        @Override\r
-        public Object getContext() {\r
-            return context;\r
-        }\r
-\r
-        @Override\r
-        public void needSynchronization(Object object) {\r
-            if (needSync == null)\r
-                needSync = new ArrayList<>();\r
-            needSync.add(object);\r
-        }\r
-\r
-        @Override\r
-        public List<Object> needSynchronization() {\r
-            return needSync;\r
-        }\r
-       }\r
-       \r
-       public static MutableVariant createVariant() {\r
-               return new MutableVariant();\r
-       }\r
-\r
-       public static Variant getSafeClientVariant(ClientModel clientModel, String location, String property) {\r
-               try {\r
-                       return clientModel.getPossiblePropertyAt(location, property);\r
-               } catch (Throwable t) {\r
-                       Logger.defaultLogError(t);\r
-                       return Variant.ofInstance(t.getMessage());\r
-               }\r
-       }\r
-\r
-    public static Resource createSheet(WriteGraph graph, Resource book, String name) throws DatabaseException {\r
-       \r
-       return createSheet(graph, book, name, new String[] {}, new int[] {});\r
-       \r
-    }\r
-\r
-       public static Resource createSheet(WriteGraph graph, Resource book, String name, String[] colNames, int[] colWidths) throws DatabaseException {\r
-\r
-        Layer0 L0 = Layer0.getInstance(graph);\r
-        SpreadsheetResource sr = SpreadsheetResource.getInstance(graph);\r
-\r
-        Resource result = graph.newResource();\r
-        graph.claim(result, L0.InstanceOf, null, sr.Spreadsheet);\r
-\r
-        if(name == null) {\r
-            name = NameUtils.findFreshEscapedName(graph, "Sheet", book, L0.ConsistsOf);\r
-        }\r
-        graph.claimLiteral(result, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);\r
-        graph.claim(book, L0.ConsistsOf, L0.PartOf, result);\r
-\r
-//            Resource newCell = graph.newResource();\r
-//            graph.claim(newCell, L0.InstanceOf, null, sr.Lines);\r
-//            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Lines", Bindings.STRING);\r
-//            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);\r
-//            BTree bt = new BTree(graph, SpreadsheetUtils.SPREADSHEET_BTREE_SIZE, SR.Lines, SR.LineNode, L0.PartOf, true);\r
-        \r
-        BTree bt = new BTree(graph, SpreadsheetUtils.SPREADSHEET_BTREE_SIZE, sr.Lines, sr.LineNode, L0.PartOf, false);\r
-//        BTree bt = BTreeUtils.create(graph, sr.Lines, sr.LineNode, L0.PartOf, SpreadsheetUtils.SPREADSHEET_BTREE_SIZE, false);\r
-        Resource lines = bt.rootOfBTree();\r
-        \r
-        graph.claimLiteral(lines, L0.HasName, L0.NameOf, L0.String, "Lines", Bindings.STRING);\r
-        graph.claim(result, L0.ConsistsOf, L0.PartOf, lines);\r
-        \r
-        {\r
-            Resource newCell = graph.newResource();\r
-            graph.claim(newCell, L0.InstanceOf, null, sr.Dimensions);\r
-            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Dimensions", Bindings.STRING);\r
-            graph.addLiteral(newCell, sr.Dimensions_fitColumns, sr.Dimensions_fitColumns_Inverse, L0.Boolean, false, Bindings.BOOLEAN);\r
-            graph.addLiteral(newCell, sr.Dimensions_fitRows, sr.Dimensions_fitRows_Inverse, L0.Boolean, false, Bindings.BOOLEAN);\r
-            graph.addLiteral(newCell, sr.Dimensions_columnCount, sr.Dimensions_columnCount_Inverse, L0.Integer, 128, Bindings.INTEGER);\r
-            graph.addLiteral(newCell, sr.Dimensions_rowCount, sr.Dimensions_rowCount_Inverse, L0.Integer, 256, Bindings.INTEGER);\r
-            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);\r
-        }\r
-\r
-        {\r
-            Resource newCell = graph.newResource();\r
-            graph.claim(newCell, L0.InstanceOf, null, sr.Headers);\r
-            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Headers", Bindings.STRING);\r
-            graph.addLiteral(newCell, sr.Headers_columnLabels, sr.Headers_columnLabels_Inverse, L0.StringArray, colNames, Bindings.STRING_ARRAY);\r
-            graph.addLiteral(newCell, sr.Headers_columnWidths, sr.Headers_columnWidths_Inverse, L0.IntegerArray, colWidths, Bindings.INT_ARRAY);\r
-            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);\r
-        }\r
-\r
-        return result;\r
-\r
-    }\r
-    \r
-\r
-    public static Variable getBookVariable(ReadGraph graph, Resource book) throws DatabaseException {\r
-       Variable variable = Variables.getVariable(graph, book);\r
-       return ProxyVariables.makeProxyVariable(graph, variable, variable);\r
-    }\r
-\r
-    public static Variable sheetRun(ReadGraph graph, Resource book, Variable context) throws DatabaseException {\r
-       Variable root = Variables.getVariable(graph, book);\r
-       return ProxyVariables.makeProxyVariable(graph, root, context);\r
-    }\r
-\r
-    private static TableCell constructCell(int row, int column, Object data) {\r
-               TableCell cell = new TableCell();\r
-               cell.row = row;\r
-               cell.column = column;\r
-               cell.text = data.toString();\r
-               return cell;\r
-    }\r
-    \r
-    public static List<TableCell> queryCells(Object data) {\r
-       ArrayList<TableCell> result = new ArrayList<TableCell>();\r
-       if(data instanceof List) {\r
-               List<?> list = (List<?>)data;\r
-               int row = 0;\r
-               for(Object o : list) {\r
-                       if(o instanceof Tuple) {\r
-                               Tuple t = (Tuple)o;\r
-                               for(int i=0;i<t.length();i++) {\r
-                                       result.add(constructCell(row, i, t.get(i)));\r
-                               }\r
-                       } else if (o instanceof List) {\r
-                               List<?> rowList = (List<?>)o;\r
-                               int index = 0;\r
-                               for(Object obj : rowList) {\r
-                                       result.add(constructCell(row, index++, obj));\r
-                               }\r
-                       } else {\r
-                                       result.add(constructCell(row, 0, o));\r
-                       }\r
-                       row++;\r
-               }\r
-       }\r
-       return result;\r
-    }\r
-    \r
-    public static List<TableCell> organizeCells(int columns, List<String> headers_, List<TableCell> cells) throws DatabaseException {\r
-       \r
-       ArrayList<TableCell> result = new ArrayList<TableCell>();\r
-       \r
-       int w = 0; // name + fields \r
-       int h = 0; // number or rows excluding headers\r
-       \r
-       if(columns < 2) throw new DatabaseException("organizeCells: number of columns needs to be greater than 1");\r
-       \r
-       for(TableCell cell : cells) {\r
-               if((cell.column+1)>w) w = cell.column+1;\r
-               if((cell.row)>h) h = cell.row;\r
-       }\r
-       \r
-       int fields = w - 1;\r
-       \r
-       if(columns > (fields + 1)) columns = fields + 1;//throw new DatabaseException("organizeCells: number of block columns cannot be greater than the amount of columns in data");\r
-       \r
-       int fieldsPerRow = columns - 1;\r
-       \r
-       int blocks = fields / fieldsPerRow;\r
-       if(fields%fieldsPerRow > 0) blocks++;\r
-\r
-       TableCell[] names = new TableCell[h];\r
-       TableCell[] headers = new TableCell[w];\r
-       \r
-       for(TableCell cell : cells) {\r
-               \r
-               if(cell.row == 0) {\r
-                       headers[cell.column] = cell;\r
-               } else if(cell.column == 0) {\r
-                       names[cell.row-1] = cell;\r
-               } else {\r
-                       TableCell copy = new TableCell(cell);\r
-                       int block = (copy.column-1) / fieldsPerRow;\r
-                       copy.row = block*(h+1) + copy.row;\r
-                               copy.column = 1 + (copy.column-1) % fieldsPerRow;\r
-                       result.add(copy);\r
-               }\r
-               \r
-       }\r
-       \r
-               for(int j=0;j<blocks;j++) {\r
-\r
-                       int rowBase = j*(h+1);\r
-                       \r
-                       for(int i=0;i<h;i++) {\r
-                       TableCell copy = new TableCell(names[i]);\r
-                       copy.row = rowBase + copy.row;\r
-                       result.add(copy);\r
-               }\r
-                       \r
-                       TableCell legend = new TableCell(headers[0]);\r
-                       legend.row = rowBase;\r
-                       result.add(legend);\r
-\r
-                       for(int i=1;i<columns;i++) {\r
-\r
-                               int index = (j*fieldsPerRow) + i;\r
-                               if(index >= w) continue;\r
-                               \r
-                               TableCell header = new TableCell(headers[index]);\r
-                               header.row = rowBase;\r
-                               header.column = i;\r
-                               result.add(header);\r
-                               \r
-               }\r
-                       \r
-       }\r
-       \r
-       return result;\r
-       \r
-    }\r
-    \r
-    public static List<TableCell> modifyCells1(List<TableCell> cells, Function1<TableCell, TableCell> fn) {\r
-       ArrayList<TableCell> result = new ArrayList<TableCell>();\r
-       for(TableCell cell : cells)\r
-               result.add(fn.apply(cell));\r
-       return result;\r
-    }\r
-\r
-    public static List<TableCell> modifyCells(List<TableCell> cells, List<Function1<TableCell, TableCell>> fns) {\r
-       ArrayList<TableCell> result = new ArrayList<TableCell>();\r
-       for(TableCell cell : cells) {\r
-               for(Function1<TableCell,TableCell> fn : fns)\r
-                       cell = fn.apply(cell);\r
-               result.add(cell);\r
-       }\r
-       return result;\r
-    }\r
-\r
-    public static TableCell applyFont(IFont font, Function1<TableCell,Boolean> filter, TableCell cell) {\r
-       if(!filter.apply(cell)) return cell;\r
-       TableCell result = new TableCell(cell);\r
-       result.font = font;\r
-       return result;\r
-    }\r
-\r
-    public static TableCell applyAlign(int align, Function1<TableCell,Boolean> filter, TableCell cell) {\r
-       if(!filter.apply(cell)) return cell;\r
-       TableCell result = new TableCell(cell);\r
-       result.align = align;\r
-       return result;\r
-    }\r
-\r
-    public static TableCell applyForeground(IColor color, Function1<TableCell,Boolean> filter, TableCell cell) {\r
-       if(!filter.apply(cell)) return cell;\r
-       TableCell result = new TableCell(cell);\r
-       result.foreground = color;\r
-       return result;\r
-    }\r
-\r
-    public static TableCell applyBackground(IColor color,Function1<TableCell,Boolean> filter,  TableCell cell) {\r
-       if(!filter.apply(cell)) return cell;\r
-       TableCell result = new TableCell(cell);\r
-       result.background = color;\r
-       return result;\r
-    }\r
-\r
-    public static IFont simpleFont(String family, String style, int height) {\r
-       return new SimpleFont(family, style, height);\r
-    }\r
-\r
-    public static IColor rgbColor(int r, int g, int b) {\r
-       return new RGBColor(r, g, b);\r
-    }\r
-\r
-    public static boolean selectRow(int row, TableCell cell) {\r
-       return cell.row == row;\r
-    }\r
-\r
-    public static boolean selectColumn(int column, TableCell cell) {\r
-       return cell.column == column;\r
-    }\r
-\r
-    public static void setSCLLine(WriteGraph graph, Resource spreadsheet, int row, String expression) throws DatabaseException {\r
-\r
-       Layer0 L0 = Layer0.getInstance(graph);\r
-       Resource lines = graph.syncRequest(new PossibleChild(spreadsheet, "Lines"));\r
-       BTree bt = new BTree(graph, lines);\r
-       SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);\r
-       \r
-       Resource line = graph.newResource();\r
-       graph.claim(line, L0.InstanceOf, SR.Line);\r
-       graph.addLiteral(line, L0.HasName, L0.NameOf, "" + row, Bindings.STRING);\r
-       Layer0Utils.setExpression(graph, line, SR.Line_content, null, "[spreadsheetCell ]", L0.SCLValue);\r
-       bt.insertBTree(graph, Variant.ofInstance(row), line);\r
-       \r
-    }\r
-\r
-    public static String getFormattedLabel(ClientModel model, int row, int column, int formatIndex, String formatString) {\r
-        if (formatString == null)\r
-            return getLabel(model, row, column); \r
-        try {\r
-            String location = SpreadsheetUtils.cellName(row, column);\r
-            Variant content = SpreadsheetUtils.getSafeClientVariant(model, location, ClientModel.CONTENT);\r
-            if(content != null) {\r
-                \r
-                String contentString = SpreadsheetUtils.getContentString(content);\r
-                if(contentString.equals("~CIRCULAR~REF~"))\r
-                       return "0";\r
-                \r
-                double value = Double.valueOf(contentString);\r
-                if (Double.isNaN(value))\r
-                       return getLabel(model, row, column);\r
-                \r
-                DataFormatter formatter = new DataFormatter();\r
-                return formatter.formatRawCellContents(value, formatIndex, formatString);\r
-            }\r
-            return null;\r
-        } catch (NumberFormatException e) {\r
-            return getLabel(model, row, column);\r
-        } catch (Throwable e) {\r
-            e.printStackTrace();\r
-            return null;\r
-        }\r
-    }\r
-    \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.spreadsheet.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.error.BindingException;
+import org.simantics.databoard.binding.mutable.MutableVariant;
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.datatypes.utils.BTree;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.PossibleChild;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.Logger;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.AssumptionException;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.VariableException;
+import org.simantics.db.layer0.util.Layer0Utils;
+import org.simantics.db.layer0.variable.ProxyVariables;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.VariableSpaceManipulator.PropertyCreationData;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.db.request.Write;
+import org.simantics.db.service.SerialisationSupport;
+import org.simantics.document.server.io.IColor;
+import org.simantics.document.server.io.IFont;
+import org.simantics.document.server.io.RGBColor;
+import org.simantics.document.server.io.SimpleFont;
+import org.simantics.layer0.Layer0;
+import org.simantics.scl.runtime.function.Function1;
+import org.simantics.scl.runtime.tuple.Tuple;
+import org.simantics.spreadsheet.ClientModel;
+import org.simantics.spreadsheet.OperationMode;
+import org.simantics.spreadsheet.Range;
+import org.simantics.spreadsheet.Spreadsheets;
+import org.simantics.spreadsheet.Transaction;
+import org.simantics.spreadsheet.common.TableCell;
+import org.simantics.spreadsheet.common.cell.StringCellParser;
+import org.simantics.spreadsheet.resource.SpreadsheetResource;
+import org.simantics.utils.datastructures.Pair;
+
+public class SpreadsheetUtils {
+
+       public static Pair<String, Collection<PropertyCreationData>> parse(String text, StringCellParser[] parsers) {
+
+               try {
+
+                       for(StringCellParser parser : parsers) { 
+                               Collection<PropertyCreationData> parsed = parser.parse(text);
+                               if(parsed != null) return Pair.make(parser.getType(), parsed);
+                       }
+
+               } catch (Throwable t) {
+                       t.printStackTrace();
+               }
+
+               return null;
+
+       }
+
+       public static boolean isImmutable(Object object) {
+               return !(object instanceof Resource) && !(object instanceof Variable); 
+       }
+
+       public static String getLabel(ReadGraph graph, Object object) throws DatabaseException {
+
+               if(object == null) {
+                       return "no data";
+               }
+
+               if(object instanceof Resource) {
+                       return NameUtils.getSafeName(graph, (Resource)object);
+               } else if (object instanceof Variable) {
+                       try {
+                               Object value = ((Variable)object).getValue(graph);
+                               return value.toString();
+                               //return toString(value);
+                       } catch (VariableException e) {
+                               Object value = ((Variable)object).getPropertyValue(graph, "Label"); 
+                               return value.toString();
+                       }
+               } else if (object instanceof double[]) {
+                       return object.toString();
+                       //                      return toString(object); 
+               } else {
+                       return object.toString();
+               }
+
+       }
+
+       private static String toString(Object object) {
+               if(object instanceof double[]) {
+                       try {
+                               return Bindings.DOUBLE_ARRAY.toString(object);
+                       } catch (BindingException e) {
+                               return object.toString();
+                       }
+               } else {
+                       return object.toString();
+               }
+       }
+
+       public static String getContent(ReadGraph graph, Object object) throws DatabaseException {
+
+               if(object == null) {
+                       return null;
+               }
+
+               if(object instanceof Resource) {
+                       SerialisationSupport support = graph.getService(SerialisationSupport.class);
+                       return support.getResourceSerializer().createRandomAccessId((Resource)object);
+               } else if (object instanceof Variable) {
+                       return ((Variable)object).getURI(graph);
+               } else {
+                       return "";
+               }
+
+       }
+
+       public static void main(String[] args) {
+               for(int i=0;i<16384;i++) {
+                       String name = Spreadsheets.columnName(i);
+                       Range r = Spreadsheets.decodeCellAbsolute(name + "1");
+                       System.err.println(i + " " + name + " " + r);
+               }
+       }
+
+       public static String getLabel(ClientModel model, int row, int column) {
+               try {
+                       String location = Spreadsheets.cellName(row, column);
+                       String label = model.getPropertyAt(location, ClientModel.LABEL);
+                       if(label != null) return label;
+                       Variant content = SpreadsheetUtils.getSafeClientVariant(model, location, ClientModel.CONTENT);
+                       if(content != null) return SpreadsheetUtils.getContentString(content);
+                       else return null;
+               } catch (Throwable e) {
+                       e.printStackTrace();
+                       return null;
+               }
+       }
+
+       public static String getContentString(Variant content) {
+               return content.getValue().toString();
+       }
+
+       public static boolean isInBounds(String base, String location, int wBounds, int hBounds) {
+               Range baseRange = Spreadsheets.decodeCellAbsolute(base);
+               Range locationRange = Spreadsheets.decodeCellAbsolute(location);
+               if(locationRange.startColumn < baseRange.startColumn) return false;
+               if(locationRange.startRow < baseRange.startRow) return false;
+               int wb = wBounds == -1 ? (Integer.MAX_VALUE / 3) : wBounds;
+               int hb = hBounds == -1 ? (Integer.MAX_VALUE / 3) : hBounds;
+               if(locationRange.startColumn > (baseRange.startColumn+wb-1)) return false;
+               if(locationRange.startRow > (baseRange.startRow+hb-1)) return false;
+               return true;
+       }
+
+       public static void schedule(Transaction<?> transaction, Write write) {
+
+               if(transaction == null) {
+
+                       TransactionImpl impl = (TransactionImpl)startTransaction(OperationMode.OPERATION);
+                       impl.add(write);
+                       impl.commit();
+
+               } else {
+
+                       TransactionImpl impl = (TransactionImpl)transaction;
+                       impl.add(write);
+
+               }
+
+       }
+       
+       public static Transaction<Write> startTransaction() {
+               return startTransaction(OperationMode.EDIT_MODE);
+       }
+
+       public static Transaction<Write> startTransaction(OperationMode mode) {
+               return new TransactionImpl(mode);
+       }       
+
+       static class TransactionImpl implements Transaction<Write> {
+
+               private ArrayList<Write> writes = new ArrayList<>();
+               private final OperationMode mode;
+        private Object context;
+        private List<Object> needSync;
+               
+               public TransactionImpl(OperationMode mode) {
+                   this.mode = mode;
+        }
+
+               public void commit() {
+
+                       Simantics.async(new WriteRequest() {
+
+                               @Override
+                               public void perform(WriteGraph graph) throws DatabaseException {
+                           graph.markUndoPoint();
+                           for(int i=0;i<writes.size();i++) {
+                               Write write = writes.get(i);
+                               try {
+                                   write.perform(graph);
+                               } catch (DatabaseException e) {
+                                   e.printStackTrace();
+                                   Logger.defaultLogError(e);
+                               }
+                               // This can schedule more writes
+                               //graph.syncRequest(write);
+                           }
+                           writes.clear();
+                               }
+                       });
+               }
+
+               @Override
+               public void add(Write write) {
+                       writes.add(write);
+               }
+
+        @Override
+        public boolean isOperationMode() {
+            return mode.equals(OperationMode.OPERATION);
+        }
+
+        @Override
+        public void setContext(Object context) {
+            this.context = context;
+        }
+
+        @Override
+        public Object getContext() {
+            return context;
+        }
+
+        @Override
+        public void needSynchronization(Object object) {
+            if (needSync == null)
+                needSync = new ArrayList<>();
+            needSync.add(object);
+        }
+
+        @Override
+        public List<Object> needSynchronization() {
+            return needSync;
+        }
+       }
+       
+       public static MutableVariant createVariant() {
+               return new MutableVariant();
+       }
+
+       public static Variant getSafeClientVariant(ClientModel clientModel, String location, String property) {
+               try {
+                       return clientModel.getPossiblePropertyAt(location, property);
+               } catch (Throwable t) {
+                       Logger.defaultLogError(t);
+                       return Variant.ofInstance(t.getMessage());
+               }
+       }
+
+    public static Resource createSheet(WriteGraph graph, Resource book, String name) throws DatabaseException {
+       
+       return createSheet(graph, book, name, new String[] {}, new int[] {});
+       
+    }
+
+       public static Resource createSheet(WriteGraph graph, Resource book, String name, String[] colNames, int[] colWidths) throws DatabaseException {
+
+        Layer0 L0 = Layer0.getInstance(graph);
+        SpreadsheetResource sr = SpreadsheetResource.getInstance(graph);
+
+        Resource result = graph.newResource();
+        graph.claim(result, L0.InstanceOf, null, sr.Spreadsheet);
+
+        if(name == null) {
+            name = NameUtils.findFreshEscapedName(graph, "Sheet", book, L0.ConsistsOf);
+        }
+        graph.claimLiteral(result, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
+        graph.claim(book, L0.ConsistsOf, L0.PartOf, result);
+
+//            Resource newCell = graph.newResource();
+//            graph.claim(newCell, L0.InstanceOf, null, sr.Lines);
+//            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Lines", Bindings.STRING);
+//            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);
+//            BTree bt = new BTree(graph, SpreadsheetUtils.SPREADSHEET_BTREE_SIZE, SR.Lines, SR.LineNode, L0.PartOf, true);
+        
+        BTree bt = new BTree(graph, Spreadsheets.SPREADSHEET_BTREE_SIZE, sr.Lines, sr.LineNode, L0.PartOf, false);
+//        BTree bt = BTreeUtils.create(graph, sr.Lines, sr.LineNode, L0.PartOf, SpreadsheetUtils.SPREADSHEET_BTREE_SIZE, false);
+        Resource lines = bt.rootOfBTree();
+        
+        graph.claimLiteral(lines, L0.HasName, L0.NameOf, L0.String, "Lines", Bindings.STRING);
+        graph.claim(result, L0.ConsistsOf, L0.PartOf, lines);
+        
+        {
+            Resource newCell = graph.newResource();
+            graph.claim(newCell, L0.InstanceOf, null, sr.Dimensions);
+            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Dimensions", Bindings.STRING);
+            graph.addLiteral(newCell, sr.Dimensions_fitColumns, sr.Dimensions_fitColumns_Inverse, L0.Boolean, false, Bindings.BOOLEAN);
+            graph.addLiteral(newCell, sr.Dimensions_fitRows, sr.Dimensions_fitRows_Inverse, L0.Boolean, false, Bindings.BOOLEAN);
+            graph.addLiteral(newCell, sr.Dimensions_columnCount, sr.Dimensions_columnCount_Inverse, L0.Integer, 128, Bindings.INTEGER);
+            graph.addLiteral(newCell, sr.Dimensions_rowCount, sr.Dimensions_rowCount_Inverse, L0.Integer, 256, Bindings.INTEGER);
+            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);
+        }
+
+        {
+            Resource newCell = graph.newResource();
+            graph.claim(newCell, L0.InstanceOf, null, sr.Headers);
+            graph.claimLiteral(newCell, L0.HasName, L0.NameOf, L0.String, "Headers", Bindings.STRING);
+            graph.addLiteral(newCell, sr.Headers_columnLabels, sr.Headers_columnLabels_Inverse, L0.StringArray, colNames, Bindings.STRING_ARRAY);
+            graph.addLiteral(newCell, sr.Headers_columnWidths, sr.Headers_columnWidths_Inverse, L0.IntegerArray, colWidths, Bindings.INT_ARRAY);
+            graph.claim(result, L0.ConsistsOf, L0.PartOf, newCell);
+        }
+
+        return result;
+
+    }
+    
+
+    public static Variable getBookVariable(ReadGraph graph, Resource book) throws DatabaseException {
+       Variable variable = Variables.getVariable(graph, book);
+       return ProxyVariables.makeProxyVariable(graph, variable, variable);
+    }
+
+    public static Variable sheetRun(ReadGraph graph, Resource book, Variable context) throws DatabaseException {
+       Variable root = Variables.getVariable(graph, book);
+       return ProxyVariables.makeProxyVariable(graph, root, context);
+    }
+
+    private static TableCell constructCell(int row, int column, Object data) {
+               TableCell cell = new TableCell();
+               cell.row = row;
+               cell.column = column;
+               cell.text = data.toString();
+               return cell;
+    }
+    
+    public static List<TableCell> queryCells(Object data) {
+       ArrayList<TableCell> result = new ArrayList<TableCell>();
+       if(data instanceof List) {
+               List<?> list = (List<?>)data;
+               int row = 0;
+               for(Object o : list) {
+                       if(o instanceof Tuple) {
+                               Tuple t = (Tuple)o;
+                               for(int i=0;i<t.length();i++) {
+                                       result.add(constructCell(row, i, t.get(i)));
+                               }
+                       } else if (o instanceof List) {
+                               List<?> rowList = (List<?>)o;
+                               int index = 0;
+                               for(Object obj : rowList) {
+                                       result.add(constructCell(row, index++, obj));
+                               }
+                       } else {
+                                       result.add(constructCell(row, 0, o));
+                       }
+                       row++;
+               }
+       }
+       return result;
+    }
+    
+    public static List<TableCell> organizeCells(int columns, List<String> headers_, List<TableCell> cells) throws DatabaseException {
+       
+       ArrayList<TableCell> result = new ArrayList<TableCell>();
+       
+       int w = 0; // name + fields 
+       int h = 0; // number or rows excluding headers
+       
+       if(columns < 2) throw new AssumptionException("organizeCells: number of columns needs to be greater than 1");
+       
+       for(TableCell cell : cells) {
+               if((cell.column+1)>w) w = cell.column+1;
+               if((cell.row)>h) h = cell.row;
+       }
+       
+       int fields = w - 1;
+       
+       if(columns > (fields + 1)) columns = fields + 1;//throw new DatabaseException("organizeCells: number of block columns cannot be greater than the amount of columns in data");
+       
+       int fieldsPerRow = columns - 1;
+       
+       int blocks = fields / fieldsPerRow;
+       if(fields%fieldsPerRow > 0) blocks++;
+
+       TableCell[] names = new TableCell[h];
+       TableCell[] headers = new TableCell[w];
+       
+       for(TableCell cell : cells) {
+               
+               if(cell.row == 0) {
+                       headers[cell.column] = cell;
+               } else if(cell.column == 0) {
+                       names[cell.row-1] = cell;
+               } else {
+                       TableCell copy = new TableCell(cell);
+                       int block = (copy.column-1) / fieldsPerRow;
+                       copy.row = block*(h+1) + copy.row;
+                               copy.column = 1 + (copy.column-1) % fieldsPerRow;
+                       result.add(copy);
+               }
+               
+       }
+       
+               for(int j=0;j<blocks;j++) {
+
+                       int rowBase = j*(h+1);
+                       
+                       for(int i=0;i<h;i++) {
+                       TableCell copy = new TableCell(names[i]);
+                       copy.row = rowBase + copy.row;
+                       result.add(copy);
+               }
+                       
+                       TableCell legend = new TableCell(headers[0]);
+                       legend.row = rowBase;
+                       result.add(legend);
+
+                       for(int i=1;i<columns;i++) {
+
+                               int index = (j*fieldsPerRow) + i;
+                               if(index >= w) continue;
+                               
+                               TableCell header = new TableCell(headers[index]);
+                               header.row = rowBase;
+                               header.column = i;
+                               result.add(header);
+                               
+               }
+                       
+       }
+       
+       return result;
+       
+    }
+    
+    public static List<TableCell> modifyCells1(List<TableCell> cells, Function1<TableCell, TableCell> fn) {
+       ArrayList<TableCell> result = new ArrayList<TableCell>();
+       for(TableCell cell : cells)
+               result.add(fn.apply(cell));
+       return result;
+    }
+
+    public static List<TableCell> modifyCells(List<TableCell> cells, List<Function1<TableCell, TableCell>> fns) {
+       ArrayList<TableCell> result = new ArrayList<TableCell>();
+       for(TableCell cell : cells) {
+               for(Function1<TableCell,TableCell> fn : fns)
+                       cell = fn.apply(cell);
+               result.add(cell);
+       }
+       return result;
+    }
+
+    public static TableCell applyFont(IFont font, Function1<TableCell,Boolean> filter, TableCell cell) {
+       if(!filter.apply(cell)) return cell;
+       TableCell result = new TableCell(cell);
+       result.font = font;
+       return result;
+    }
+
+    public static TableCell applyAlign(int align, Function1<TableCell,Boolean> filter, TableCell cell) {
+       if(!filter.apply(cell)) return cell;
+       TableCell result = new TableCell(cell);
+       result.align = align;
+       return result;
+    }
+
+    public static TableCell applyForeground(IColor color, Function1<TableCell,Boolean> filter, TableCell cell) {
+       if(!filter.apply(cell)) return cell;
+       TableCell result = new TableCell(cell);
+       result.foreground = color;
+       return result;
+    }
+
+    public static TableCell applyBackground(IColor color,Function1<TableCell,Boolean> filter,  TableCell cell) {
+       if(!filter.apply(cell)) return cell;
+       TableCell result = new TableCell(cell);
+       result.background = color;
+       return result;
+    }
+
+    public static IFont simpleFont(String family, String style, int height) {
+       return new SimpleFont(family, style, height);
+    }
+
+    public static IColor rgbColor(int r, int g, int b) {
+       return new RGBColor(r, g, b);
+    }
+
+    public static boolean selectRow(int row, TableCell cell) {
+       return cell.row == row;
+    }
+
+    public static boolean selectColumn(int column, TableCell cell) {
+       return cell.column == column;
+    }
+
+    public static void setSCLLine(WriteGraph graph, Resource spreadsheet, int row, String expression) throws DatabaseException {
+
+       Layer0 L0 = Layer0.getInstance(graph);
+       Resource lines = graph.syncRequest(new PossibleChild(spreadsheet, "Lines"));
+       BTree bt = new BTree(graph, lines);
+       SpreadsheetResource SR = SpreadsheetResource.getInstance(graph);
+       
+       Resource line = graph.newResource();
+       graph.claim(line, L0.InstanceOf, SR.Line);
+       graph.addLiteral(line, L0.HasName, L0.NameOf, "" + row, Bindings.STRING);
+       Layer0Utils.setExpression(graph, line, SR.Line_content, null, "[spreadsheetCell ]", L0.SCLValue);
+       bt.insertBTree(graph, Variant.ofInstance(row), line);
+       
+    }
+
+    public static String getFormattedLabel(ClientModel model, int row, int column, int formatIndex, String formatString) {
+        if (formatString == null)
+            return getLabel(model, row, column); 
+        try {
+            String location = Spreadsheets.cellName(row, column);
+            Variant content = SpreadsheetUtils.getSafeClientVariant(model, location, ClientModel.CONTENT);
+            if(content != null) {
+                
+                String contentString = SpreadsheetUtils.getContentString(content);
+                if(contentString.equals("~CIRCULAR~REF~"))
+                       return "0";
+                
+                double value = Double.valueOf(contentString);
+                if (Double.isNaN(value))
+                       return getLabel(model, row, column);
+                
+                DataFormatter formatter = new DataFormatter();
+                return formatter.formatRawCellContents(value, formatIndex, formatString);
+            }
+            return null;
+        } catch (NumberFormatException e) {
+            return getLabel(model, row, column);
+        } catch (Throwable e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /*
+     *  Please use Spreadsheets.cellName instead
+     */
+    @Deprecated
+    public static String cellName(int row, int column) {
+        return Spreadsheets.cellName(row, column);
+    }
+
+    /*
+     *  Please use Spreadsheets.columnName instead
+     */
+    @Deprecated
+    public static String columnName(int column) {
+        return Spreadsheets.columnName(column);
+    }
+
+    /*
+     *  Please use Spreadsheets.decodeCellAbsolute instead
+     */
+    @Deprecated
+    public static Range decodeCellAbsolute(String identifier) {
+        return Spreadsheets.decodeCellAbsolute(identifier);
+    }
+    
+    /*
+     *  Please use Spreadsheets.decodePossibleCellAbsolute instead
+     */
+    @Deprecated
+    public static Range decodePossibleCellAbsolute(String identifier) {
+        return Spreadsheets.decodePossibleCellAbsolute(identifier);
+    }
+
+    /*
+     *  Please use Spreadsheets.decodeRange instead
+     */
+    @Deprecated
+    public static Range decodeRange(String rangeOrCell) {
+        return Spreadsheets.decodeRange(rangeOrCell);
+    }
+
+    /*
+     *  Please use Spreadsheets.decodeRanges instead
+     */
+    @Deprecated
+    public static List<Range> decodeRanges(String ranges) {
+        return Spreadsheets.decodeRanges(ranges);
+    }
+    
+    /*
+     *  Please use Spreadsheets.startColumn instead
+     */
+    @Deprecated
+    public static int startColumn(List<Range> ranges) {
+        return Spreadsheets.startColumn(ranges);
+    }
+
+    /*
+     *  Please use Spreadsheets.startRow instead
+     */
+    @Deprecated
+    public static int startRow(List<Range> ranges) {
+        return Spreadsheets.startRow(ranges);
+    }
+    
+    /*
+     *  Please use Spreadsheets.offset instead
+     */
+    @Deprecated
+    public static String offset(String location, int rowOffset, int columnOffset) {
+        return Spreadsheets.offset(location, rowOffset, columnOffset);
+    }
+
+    
+}